views:

68

answers:

1

Hmm... Title is a bit of a mouthful, but I'm really not sure which part of this is causing issues, I've run through it a ton of times, and can't pinpoint why...

The idea is for a single Choice instance to be able to store any one value of any of the types passed in to it's template list... It's kind of like a union, except it keeps track of the type being stored, and considers values of each type to be distinct, which allows it to get around the C++ constraints on constructors in union members.

It does work in some cases, but there seems to be some problems with the cleanup code. I started getting segfaults the second I started using this structure with std::basic_string or similar types passed in the argument list, but I can't see why that would cause any issues.

This is kind of a though experiment for myself, but I can't see any reason why it shouldn't work (compiled in C++0x mode in g++):

// virtual methods should provide a way of "remembering"
// the type stored within the choice at any given time
struct ChoiceValue
{
   virtual void del(void* value) = 0;
   virtual bool is(int choice) = 0;
};

// Choices are initialized with an instance
// of this structure in their choice buffer
// which should handle the uninitialized case
struct DefaultChoiceValue : public IChoiceValue
{
   virtual void del(void* value) {}
   virtual bool is(int choice) { return false; }
};

// When a choice is actually initialized with a value
// an instance of this structure (with the appropriate value
// for T and TChoice) is created and stored in the choice
// buffer, allowing it to be cleaned up later (using del())
template<int TChoice, typename T>
struct ChoiceValue
{
    virtual void del(void* value) { ((T*)value)->~T(); }
    virtual bool is(int choice) { return choice == TChoice; }
};

template<typename ... TAll>
struct Choice
{
};

template<typename T1, typename ... TRest>
struct Choice<T1, TRest...>
{
  // these two constants should compute the buffer size needed to store
  // the largest possible value for the choice and the actual value
  static const int CSize = sizeof(ChoiceValue<0, T1>) > Choice<TRest...>::CSize
         ? sizeof(ChoiceValue<0, T1>) : Choice<TRest...>::CSize;
  static const int VSize = sizeof(T1) > Choice<TRest...>::VSize
         ? sizeof(T1) : Choice<TRest...>::VSize;

   IChoiceValue* _choice;
   char* _choiceBuffer;
   char* _valueBuffer;

   Choice()
   {
      _choiceBuffer = new char[CSize];
      _valueBuffer = new char[VSize];
      _choice = new (_choiceBuffer) DefaultChoiceValue();
   }
   ~Choice()
   {
      _choice->del(_valueBuffer);
      delete[] _choiceBuffer;
      delete[] _valueBuffer;
   }
   template<int TChoice, typename T>
   T& get()
   {
      if(_choice->is(TChoice))
        return *(T*)_valueBuffer;
      else
      {
         _choice->del(_valueBuffer);
         new (_valueBuffer) T();
         _choice = new (_choiceBuffer) ChoiceValue<TChoice, T>();
         return *(T*)_valueBuffer;
      }
   }
};

template<typename T1>
struct Choice<T1>
{
  // required for the base case of a template
  // with one type argument
  static const int CSize = sizeof(ChoiceValue<0, T1>) > sizeof(DefaultChoiceValue)
              ? sizeof(ChoiceValue<0, T1>) : sizeof(DefaultChoiceValue);
  static const int VSize = sizeof(T1);

  // I have an implementation here as well in my code
  // but it is pretty much just a copy of the above code
  // used in the multiple types case
};

Thanks a ton if anyone can find out what I'm doing wrong :)

+4  A: 

You didn't post any code related to the actual crash, but I'm going to guess that you either return an instance of Choice<...> by value or invoke the copy constructor through some other means. Since you didn't define a copy constructor, you are probably double freeing the memory you allocated with Choice<...>::Choice.

MSN
Wow, total idiot move on my part, assumed other portions of the code were right :(... Now I have to hunt down where I'm doing that, as it isn't supposed to be happening. Just implemented the copy constructor and it works properly now, so that was definitely the issue
LorenVS
If instead of implementing it you declare it private and you do not provide an implementation the compiler or linker will point you to the locations where the copy constructor is being called (the compiler if the caller is not within the same class as it is private, the linker if it is within the same class, as private is still accessible but not being implemented the linker will fail)
David Rodríguez - dribeas
Nice little tip there David, thanks
LorenVS
The same applies to the assignment operator. See "rule of three" and boost::noncopyable.
sellibitze