8

How did Boost implement Tuple before C++11 and Variadic Templates?

In other words:
Is it possible to implement a Variadic Templates class or function by not using built-in Variadic Templates feature in C++11?

Flexo
  • 87,323
  • 22
  • 191
  • 272
MBZ
  • 26,084
  • 47
  • 114
  • 191
  • 2
    If I'm not mistaken, there's a new thing for each new template parameter, up to a defined maximum number of template parameters. – chris Jan 02 '13 at 02:03
  • 1
    What those macros do is create different templates for a tuple with two elements, with three elements, with four elements etc. (up to whatever limit the boost developers decided to support), avoiding the need for developers to write them by hand. – Andrei Tita Jan 02 '13 at 02:05
  • possible duplicate of [At it's core, how is boost tuple implemented (without all the extra details in the boost header)](http://stackoverflow.com/questions/10722950/at-its-core-how-is-boost-tuple-implemented-without-all-the-extra-details-in-t) – leemes Jan 02 '13 at 02:22
  • If you are interested in hacks to emulate variadic templates, I suggest you have look at the Boost Preprocessor library. The usage of `BOOST_ENUM` and co is predominant in the hackery :) – Matthieu M. Jan 02 '13 at 09:20

2 Answers2

5

Boost had a limit for the size of the tuple. As in most real-world scenarios you don't need more than 10 elements, you won't mind this limitation. As a library maintainer, I guess, the world became much simpler with variadic templates. No more macro hacks...

Here is an insightful discussion about the size limit of Boost tuple and its implementation: boost tuple: increasing maximum number of elements

To answer your second question: No, it is not possible. At least not for an unlimited number of elements.

Community
  • 1
  • 1
Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
  • 3
    Technically, C++11 doesn't allow an *unlimited* number of elements either. The difference is that the maximum number is built into the compiler, and it's *much* larger than 10. It's defined by the template recursion limit of the compiler. – Nicol Bolas Jan 02 '13 at 04:13
  • Boost.Tuple has `#define FUSION_MAX_VECTOR_SIZE 20` (for example) to increase the number of elements. – alfC May 26 '13 at 19:24
0

There are 2 common use cases I've seen, as a library developer, for variadic templates. You can build a work around for both.

Case 1: Function objects

std::function<> and lambdas are very nice, but even c++11 only gives you a fairly basic set of things you can do with them "out of the box". To implement really cool things and utilities on top of them, you need to support variadic templates because std::function can be used with any normal function signature.

Workaround: A recursive call using std::bind is your friend. It IS less efficient than real variadic templates (and some tricks like perfect forwarding probably won't work), but it'll work okay for modest #s of template arguments until you port to c++11.

Case 2: Ordinary classes

Sometimes you need an ordinary class to manage generic std::function<>s (see above) or expose an API like "printf". Workarounds here come down to details and what each API of the class is doing.

APIs that merely manipulate variadic template data but don't need to store it can run as recursive calls. You need to write them so that they "consume" one argument at a time, and stop when they run out of arguments.

APIs (including constructors) that need to STORE variadic template data are harder- you're screwed if the types are really unlimited and could be anything. BUT, if they're always going to be primitives that map deterministically to binary, you can do it. Just write a "Serialize" call taking all the types you support, then use it to serialize the entire set into a binary buffer and build a vector of "type info" data you use to fetch & set them. Its actually a better solution than std::tuple in terms of memory and performance in the special cases its available.

Here's the "serialize tuple" trick:

// MemoryBuffer: A basic byte buffer w/ its size
class MemoryBuffer {
private:
   void* buffer;
   int   size;
   int   currentSeekPt;

protected:
   void  ResizeBuffer() {
      int   newSz  = size << 1; // Multiply by 2
      void* newBuf = calloc( newSz, 1); // Make sure it is zeroed
      memcpy( newBuf, buffer, target->size);
      free( buffer);

      size = newSz; 
      buffer = newBuf;
   }

 public:
    MemoryBuffer(int initSize) 
       : buffer(0), size(initSize), currentSeekPt(0)
    {
       buffer = calloc( size, 1);
    }
   ~MemoryBuffer() {
      if(buffer) {
         free( buffer);
      }
    }

    // Add data to buffer
    bool AddData(const void* data, int dataSz) {
        if(!data || !dataSz) return false;

        if(dataSz + currentSeekPt > size) { // resize to hold data
            ResizeBuffer();
        }
        memcpy( buffer, data, dataSz);
        return true;
    }

    void* GetDataPtr() const { return buffer; }
    int   GetSeekOffset() const { return currentSeekPt; }
    int   GetTotalSize() const { return size; }
};

struct BinaryTypeInfo {
   std::type_info type;     // RTTI type_info struct. You can use an "enum"
                            // instead- code will be faster, but harder to maintain.   
   ui64      bufferOffset;  // Lets me "jump" into the buffer to 
}

// Versions of "Serialize" for all 'tuple' data types I support
template<typename BASIC>
bool Serialize(BASIC data, MemoryBuffer* target,
              std::vector<BinaryTypeInfo>& types)
{
    // Handle boneheads
    if(!target) return false;

    // Setup our type info structure
    BinaryTypeInfo info;
    info.type = typeid(data);
    info.bufferOffset = target->GetSeekOffset();

    int binarySz = sizeof(data);
    void* binaryVersion = malloc( binarySz);
    if(!binaryVersion) return false;

    memcpy( binaryVersion, &data, binarySz); // Data type must support this
    if(!target->AddData( binaryVersion, binarySz)) {
        free( binaryVersion);
        return false;
    }
    free( binaryVersion);

    // Populate type vector     
    types.push_back( info);
    return true;
}

This is just a quick & dirty version; you'd hide the real thing better and probably combine the pieces into 1 reusable class. Note that you need a special version of Serialize() if you wish to handle std::string and more complex types.

user3726672
  • 187
  • 2