GCC 4.3, C++0x preview

GCC 4.3 came out a cou­ple weeks ago, and I fi­nally got time to give its ex­per­i­men­tal C++0x sup­port a go. Specif­i­cally, I was in­ter­ested in two fea­tures of it: vari­adic tem­plates and rvalue ref­er­ences.

There is one prime ex­am­ple of what these two fea­tures are awe­some for: per­fect for­ward­ing. Take a mem­ory pool. You might have some­thing like this:

class pool {
   void* alloc();

   template<typename T>
   T* construct() { return new(alloc()) T; }
};

But that is hardly sat­is­fac­tory. What if you want to con­struct T with an ar­gu­ment?

class pool {
   void* alloc();

   template<typename T>
   T* construct() { return new(alloc()) T; }

   template<typename T, typename ArgT>
   T* construct(const ArgT &arg) { return new(alloc()) T(arg); }
};

So we add a new func­tion to han­dle pass­ing an arg. Bet­ter, but still not very great. What if you want to pass it mul­ti­ple ar­gu­ments?

C++ has very few prob­lem that can’t be worked around in a rel­a­tively strait­for­ward way. Un­for­tu­nately, this is one of those prob­lems. The cur­rent so­lu­tion most li­brary de­vel­op­ers em­ploy will in­volve some re­ally nasty pre­proces­sor hacks to gen­er­ate sep­a­rate func­tions for 1, 2, 3, up to maybe 10 or 15 ar­gu­ments. So, how do we solve this?

Enter vari­adic tem­plates, the new C++ fea­ture built specif­i­cally to solve this. Here is an up­dated pool class that takes any num­ber of ar­gu­ments:

class pool {
   void* alloc();

   template<typename T, typename Args...>
   T* construct(const Args&... args) { return new(alloc()) T(args...); }
};

Pretty sim­ple! Those el­lipses will ex­pand into zero or more args. Great – we’re al­most there. But we still have a prob­lem here: what hap­pens if the con­struc­tor for T takes some ar­gu­ments as non-const ref­er­ences? This con­struct func­tion will try to pass them as const ref­er­ences, re­sult­ing in a com­pile error. We can’t have it pass args as non-const ref­er­ences, be­cause then if you pass it an rvalue—such as a tem­po­rary—it will gen­er­ate an­other com­pile error as rval­ues can only be bound to const ref­er­ences.

This is where the sec­ond part of our pool up­grades come in: rvalue ref­er­ences.

class pool {
   void* alloc();

   template<typename T, typename Args...>
   T* construct(Args&&... args) {
      return new(alloc()) T(std::forward(args)...);
   }
};

We’ve fi­nally got our so­lu­tion. That dou­ble-ref­er­ence look­ing thing is the new syn­tax for rvalue ref­er­ences. This con­struct im­ple­ments per­fect for­ward­ing: call­ing construct<foo>(a, b, c, d) will be­have ex­actly as if we had called the con­struc­tor di­rectly via new(alloc()) T(a, b, c, d).

This works be­cause Args is a tem­plated type that will re­solve to ref­er­ences and const ref­er­ences if it needs to. One prob­lem I have yet to fig­ure out how to solve is a con­struc­tor where you know the type you want, and want to ac­cept any ref­er­ence type:

struct foo {
   foo(const bar &b) : m_b(b) {}
   foo(bar &&b) : m_b(std::move(b)) {}

   bar m_b;
};

I don’t care if b is a lvalue or rvalue ref­er­ence: I just want the con­struc­tion of m_b to be as ef­fi­cient as pos­si­ble so that it can use move se­man­tics when you pass it an rvalue. So far the only way I can find to do it is with two sep­a­rate con­struc­tors, which could mean a lot of code du­pli­ca­tion on some­thing less triv­ial than this ex­am­ple.

Posted on March 19, 2008 in C++, C++0x, Coding, GCC

Related Posts