简体   繁体   中英

Variadic Templates before C++11

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?

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM