Variadic tuple<..> — imperative meta programming
Since my last two posts (here and here) are about how … might unpack and generate code if it were very smart, I thought I’d bring up a more difficult case. How do you build templated constructors inside the variadic template tuple class?
Remember, this is all fantasy code. The … operator will not actually be able to do any of this. Also remember this problem can be solved by defining tuple using a recursive first/rest typelist scheme (see the current Boost implementation).
At first glance it might appear that it’s easy to define a constructor. Just do this.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
// Member variables - not a legal use of ...
private:
TYPEs ...
values;
// Constructor
public:
tuple( TYPEs && ... args)
: values( std::forward( args)) ...
{ }
/* .. etc .. */
};
This is pretty good, but we’d like to make all the arguments optional. So we could do this.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
// Member variables
private:
TYPEs ...
values;
// Constructor
public:
tuple( TYPEs && ... args = TYPEs( ))
: values( std::forward( args)) ...
{ }
/* .. etc .. */
};
I think the double … expansion in the parameter list works. I’m pretty sure you only specify … immediately before the variable to be packed, and that all the already-packed variables in the expression will be unpacked at the same time. So you don’t specify … again after TYPEs( ).
In any case, this still needs improvement. This requires that all TYPEs have a copy constructor even if they are going to be default constructed. We really want a bunch of constructors, taking zero, one, two, etc arguments.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
// Member variables
private:
TYPEs ...
values;
// Generate a family of constructors (not legal C++).
// If TYPEs has three types <A, B, C> this
// generates four constructors:
// constructor( )
// constructor( A &&)
// constructor( A &&, B &&)
// constructor( A &&, B &&, C &&)
for... ( size_t
N = 0
; N <= sizeof...( TYPEs )
; ++ N )
{
public:
tuple( TYPEs && unpack...( N ) args)
: values( std::forward( args)) ...
{ }
}
/* .. etc .. */
};
Yikes, that’s not C++, and we’re not in Kansas anymore. In this fantasy code for… is a meta keyword, interpreted by the compiler and producing a series of constructors. And unpack…(N) only unpacks the first N items in the packed variable TYPEs, and not the entire list.
Instead of for… in the above example we could use plain old for and say the compiler should run code that is not in a code-defining context. But we’d still need to quote or emit the tokens inside the loop above. Something like this.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
/* .. etc .. */
for ( size_t
N = 0
; N <= sizeof...( TYPEs )
; ++ N )
{
emit {
public:
tuple( TYPEs && unpack...( N ) args)
: values( std::forward( args)) ...
{ }
}
}
/* .. etc .. */
};
This implementation is still incomplete however. The constructor methods themselves should be templated so you can construct tuples from values with convertible types. With that in mind, here is the last implementation.
/* fantasy code -- will not work */
template< typename ... TYPEs >
class
tuple
{
// Member variables
private:
TYPEs ...
values;
// Generate a family of constructors.
// If TYPEs has three types <A, B, C> this
// generates four constructors with compatible
// types:
// template< >
// constructor( )
// template< typename AC >
// constructor( AC &&)
// template< typename AC, typename BC >
// constructor( AC &&, BC &&)
// template< typename AC, typename BC, typename CC >
// constructor( AC &&, BC &&, CC &&)
for... ( size_t
N = 0
; N <= sizeof...( TYPEs )
; ++ N )
{
public:
template< typename generate...( N ) ARG_TYPEs >
tuple( ARG_TYPEs && ... args)
: values( std::forward( args)) ...
{ }
}
/* .. etc .. */
};
This introduces the fantasy generate...(N) meta function to generate a template parameter list of N typenames. This also unpacks values and args at the same time (and in parallel) even though values is packed from TYPEs while args is packed from ARG_TYPES. And args usually packs fewer names than values.
This kind of imperative code generation is sometimes easier to create and understand than the declarative templates C++ currently provides. But it also adds more syntax and keywords to C++, which is plenty complicated already. And a declarative solution that isn't as restrictive as C++ templates may be a better than this imperative suggestion. So I'm not advocating any of this stuff. I'm just thinking out loud, writing it down to see what it looks like.
Comments
Leave a Reply