Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

Added new project to Loki.

Developers
2007-05-09
2013-04-08
  • Hi Everyone,

    I added a new header file to Loki - CheckReturn.h.  It contains a very simple template class and nothing else.  You can use CheckReturn to force any code which calls a function to check the return value from that function.  I got the idea from an article by Andrei in CUJ some years ago.  I also added a test project to exercise CheckReturn and demonstrate how to use it.

    Cheers,

    Rich

     
    • Peter Kuemmel
      Peter Kuemmel
      2007-05-09

      Hi Rich,
      do you know this presentation of Andrei:

      http://www.nwcpp.org/Meetings/2006/05.html ?

      Last year I read it and also started coding
      and come to this:

      // $Header:

      #include <stdio.h>
      #include <iostream>

      class None
      {
      public:
          explicit None(const char * info = "") : info_(info)
          {}

          template <class T>
          operator const T&() const
          {
              throw std::runtime_error(info_);
          }

          template <class T>
          operator  T&()
          {
              throw std::runtime_error(info_);
          }
      private:
          const char* info_;
          None& operator=(const None&);
      };

      // Lily is short for Likely
      template <class T>
      struct Lily
      {
          Lily(const None& none) : none_(none), isNone_(true), noneCompared_(false)
          {}

          Lily(const T& v) : t_(v), isNone_(false), noneCompared_(false)
          {}

          // throws if not compared against None
          ~Lily()
          {
              if(!noneCompared_)
                  throw std::runtime_error("");
          }

          operator const T&() const
          {
              if(!isNone())
                  return t_;
              else
                  return none_;
          }
         
          operator T&()
          {
              if(!isNone())
                  return t_;
              else
                  return none_;
          }

          bool isNone() const
          {
              noneCompared_ = true;
              return isNone_;
          }

          bool operator==(const None&)
          {
              return isNone();
          }

      private:
          Lily& operator=(const Lily&);
          T t_; 
          None none_;
          const bool isNone_;
          mutable bool noneCompared_;
      };

      // test 1

      typedef Lily<int> lily_int;

      lily_int returnNone(bool none)
      {
          if(!none)
              return 3145;
          else
              return None();
      }

      void test1()
      {
         
          {
              // correct usage
              lily_int r1 = returnNone(false);
              if(!r1.isNone())
              {
                  int i = r1;
                  printf("is %i \n", i);
              }
          }

          {
              // correct usage
              lily_int r2 = returnNone(true);
              if(r2.isNone())
              {
                  printf("is None \n");
              }
          }

          {
              // correct usage
              lily_int r3 = returnNone(true);
              if(r3 == None())
              {
                  printf("is None, too \n");
              }
          }
         

          try
          {
              // not checked
              {
                  lily_int r3 = returnNone(true);
              }
          }
          catch (...)
          {
              printf("exception: return value not checked\n");
          }

          {
              // not checked but dereferenced
              lily_int r4 = returnNone(true);
              try
              {
                  int i = r4;
                  i++;
              }
              catch (...)
              {
                  printf("exception: None dereferenced\n");
              }
          }

      }

      // test 2

      class A
      {
      public:
          A(){}

      // Lily only works with copyable values,
      // should we add the support of storing references? (type traits)
      //private:
          A(const A&){}
          A& operator=(const A&){}
      };

      typedef Lily<A> lily_A;

      lily_A returnNoneA(bool none)
      {
          if(!none)
              return A();
          else
              return None();
      }

      void test2()
      {
          printf("\n\n\ncopyable class test\n\n");
         
          {
              // correct usage
              lily_A r1 = returnNoneA(false);
              if(!r1.isNone())
              {
                  A& a = r1; (void)a;
                  printf("is A& returned\n");
              }
          }

          {
              // correct usage
              lily_A r2 = returnNoneA(true);
              if(r2.isNone())
              {
                  printf("is None \n");
              }
          }

          {
              // correct usage
              lily_A r3 = returnNoneA(true);
              if(r3 == None())
              {
                  printf("is None, too \n");
              }
          }
         

          try
          {
              // not checked
              {
                  lily_A r3 = returnNoneA(true);
              }
          }
          catch (...)
          {
              printf("exception: return value not checked\n");
          }

          {
              // not checked but dereferenced
              lily_A r4 = returnNoneA(true);
              try
              {
                  A& a = r4; (void)a;
              }
              catch (...)
              {
                  printf("exception: None dereferenced\n");
              }
          }

      }

      int main()
      {
          test1();
          test2();
      }

      A always had in mind to make it more complete, but never
      finished it. Now seeing your CheckReturn I think
      I could restart. ;)

      Maybe we could merge it. I think using an assert is not optimal,
      because you could not catch it. What do you think?

      Peter

       
      • Since then I have changed quite a bit about the code, making it much nicer. Here is the header, followed by a short test:

        // likely.h

        #ifndef __LIKELY_H__
        #define __LIKELY_H__

        #include <memory>
        #include <stdexcept>

        typedef char (&yes)[1];

        typedef char (&no)[2];

        template <class T>

        yes IsClassTest(void(T::*)(void));

        template <class T>

        no IsClassTest(...);

        template <class T>

        struct IsClass

        {

           enum {value = sizeof(IsClassTest<T>(0)) == sizeof(yes)};
        };

        template <bool, class T, class U>
        struct Select
        {
            typedef T Type;
        };

        template <class T, class U>
        struct Select<false, T, U>
        {
            typedef U Type;
        };

        template <typename E>
        struct Wrapper
        {
            Wrapper(const E& obj) : e(obj) {}
            operator const E&() const
            {
                return e;
            }
            const E* GetPtr() const
            {
                return &e;
            }
        private:
            E e;
        };

        struct IssueInterface
        {
            virtual ~IssueInterface() throw()
            {
            }
            virtual void Throw() const = 0;
            virtual bool IsEnabled() const = 0;
            virtual void Disable() = 0;
        };

        template <typename E>
        struct Issue : IssueInterface, Select<IsClass<E>::value, E, Wrapper<E> >::Type
        {
            Issue(const E& obj) : Select<IsClass<E>::value, E, Wrapper<E> >::Type(obj), enabled(true)
            {
            }
           
            virtual ~Issue() throw()
            {
            }

            virtual void Throw() const
            {
                if (!std::uncaught_exception())
                {
                    throw static_cast<const E&>(*this);
                }
            }

            virtual bool IsEnabled() const
            {
                return enabled;
            }

            virtual void Disable()
            {
                enabled = false;
            }

        private:
            Issue(const Issue&);
            Issue& operator =(const Issue&);
        private:
            bool enabled;
        };

        enum TLikelyInvalid {LIKELY_INVALID};

        template <typename T>
        struct Likely
        {
            Likely()
            {
                new (buffer) T();
            }

            Likely(const T& v)
            {
                new (buffer) T(v);
            }

            Likely(const Likely& obj) : spIssue(obj.spIssue)
            {
                if (!obj.spIssue.get())
                {
                    new (buffer) T(obj.RefT());
                }
            }

            template <typename E>
            Likely(const E& obj, TLikelyInvalid) : spIssue(new Issue<E>(obj))
            {
            }

            Likely& operator =(const Likely& obj)
            {
                if (&obj == this)
                    return *this;

                if (!spIssue.get())
                    RefT().~T();
                spIssue = obj.spIssue;

                if (!spIssue.get())
                    new (buffer) T(obj.RefT());
                return *this;
            }

            ~Likely()
            {
                if (!spIssue.get())
                    RefT().~T();
                ThrowIfIssueIsEnabled();
            }

            operator T&()
            {
                ThrowIfIssueIsEnabled();
                return RefT();
            }

            operator const T&() const
            {
                ThrowIfIssueIsEnabled();
                return RefT();
            }

            bool HasValue() const
            {
                if (spIssue.get())
                    spIssue->Disable();
                return !spIssue.get();
            }

            template <typename E>
            const E* Probe() const
            {
                typename Select<IsClass<E>::value, const E*, const Wrapper<E>* >::Type pPtr =
                    dynamic_cast<typename Select<IsClass<E>::value, const E*, const Wrapper<E>* >::Type>(spIssue.get());
                if (!pPtr)
                    return 0;
                const E& e = *pPtr;
                return &e;
            }

        private:
            T& RefT()
            {
                return *reinterpret_cast<T*>(buffer);
            }
            const T& RefT() const
            {
                return *reinterpret_cast<const T*>(buffer);
            }
            void ThrowIfIssueIsEnabled() const
            {
                if (spIssue.get() && spIssue->IsEnabled())
                    spIssue->Throw();
            }

        private:
        //    T val;
            char buffer[sizeof(T)];
            mutable std::auto_ptr<IssueInterface> spIssue;
        };

        template <typename T>
        inline void IgnoreIssue(const T& t)
        {
            t.HasValue();
        }

        template <typename T>
        struct Likely<T&>
        {
            Likely() : like(0)
            {
            }

            Likely(T& v) : like(&v)
            {
            }

            Likely(const Likely& obj) : like(obj.like)
            {
            }

            template <typename E>
            Likely(const E& obj, TLikelyInvalid i) : like(obj, i)
            {
            }

            operator T&()
            {
                return *like;
            }

            operator const T&() const
            {
                return *like;
            }

            bool HasValue() const
            {
                return like.HasValue();
            }

            template <typename E>
            const E* Probe() const
            {
                return like.Probe<E>();
            }
        private:
            Likely<T*> like;
        };

        #endif

        // main.cpp

        #include "likely.h"
        #include <stdexcept>

        //#include <iostream>
        #include <filelog.h>
        #include <scopeguard.h>
        // #define FILE_LOG
        // #define logINFO (std::cout << std::endl)
        // #define logERROR (std::cerr << std::endl)

        template <typename E>
        Likely<int> Atoi(const E* pError)
        {
            return pError ? Likely<int>(10) : Likely<int>(E(*pError), LIKELY_INVALID);
        }

        int main()
        {
            try
            {
                FILE_LOG(logINFO) << "Starting the test";
                ENFORCE(IsClass<int>::value == 0);
                ENFORCE(IsClass<std::string>::value != 0);

                //working with references
                int j = 1000;
                Likely<int&> res(j);
                Likely<int&> res2(100, LIKELY_INVALID);
                ENFORCE(res.HasValue());
                ENFORCE(!res2.HasValue());
                ENFORCE(res2.Probe<int>());
                int& k = res;
                j++;

                //I want to work with exceptions here, so no testing...
                int a1 = Likely<int>(10);
                Likely<int> a2 = Likely<int>(std::runtime_error("An error"), LIKELY_INVALID);
                ENFORCE(!a2.HasValue());
                FILE_LOG(logINFO) << "Handling the error here: it would be nice/convenient to be able to get at least some error string "
                    "from the IssueInterface; on the other part, if we do that, we start to assume things about the exception object...\n"
                    "Latest news: Probe() solves this problem!";
                const std::runtime_error* pError = a2.Probe<std::runtime_error>();
                ENFORCE(pError);
                FILE_LOG(logINFO) << pError->what();
                const std::exception* pErrorBase = a2.Probe<std::exception>();
                ENFORCE(pErrorBase);
                FILE_LOG(logINFO) << "It doesn't have to match the exact type :-(: '" << pErrorBase->what() << "'";

                a2.Probe<double>();
                const double* pOtherError = a2.Probe<double>();
                ENFORCE(!pOtherError);

                a2 = Likely<int>(10);
                ENFORCE(a2.HasValue());
                FILE_LOG(logINFO) << "I don't want exceptions here; after testing I can safely use the object...";
                int b = a2;

                a2 = Likely<int>(std::logic_error("Some error"), LIKELY_INVALID);
                IgnoreIssue(a2);

                std::runtime_error e("something fishy is going on");
                Likely<int> i1;
                Likely<int> i2(10);
                Likely<int> i3(b);
                i3 = i2;

                std::string s;
                Likely<std::string> s1;
                Likely<std::string> s2(s);
                s2 = s1;
                {
                    int i = 123;
                    Likely<double> d1(i, LIKELY_INVALID);
                    const int* pOtherError = d1.Probe<int>();
                    ENFORCE(pOtherError);
                    FILE_LOG(logINFO) << "This is an int exception indeed";
                    IgnoreIssue(d1);
                }//<= should we throw here?! (in the d-tor for Likely<T>)

                Likely<double> d2(3.5);

                FILE_LOG(logINFO) << "Ending the test";
                return 0;
            }
            catch (const std::exception& e)
            {
                FILE_LOG(logERROR) << e.what();       
            }
            catch (...)
            {
                FILE_LOG(logERROR) << "Unknown exception";       
            }
        }

         
        • Peter Kuemmel
          Peter Kuemmel
          2007-05-09

          Andrei, isn't the new in the constructor too expensive?
          a2 = Likely<int>(10); -> a new for an int?

          Peter

           
          • Yeah, there is a new statement in Likely's constructor - but that is a placement new so it will not cause any memory allocation.

            On the other hand, Likely's constructor calls T's constructor.  T's constructor might have side effects or require memory allocations deeper down.  I see your point that even a placement new within Likely's constructor might be expensive for minor uses of Likely.

            - Rich

             
      • Peter Kuemmel
        Peter Kuemmel
        2007-05-09

        Now I could remember what I had planned to add:
        A policy for the destructor, throw, assert, cout, logfile, ...