views:

322

answers:

2

I was wondering if C++0x provides any built-in capabilities to check if a parameter pack of a variadic template contains a specific type. Today, boost:::mpl::contains can be used to accomplish this if you are using boost::mpl::vector as a substitute for variadic templates proper. However, it has serious compilation-time overhead. I suppose, C++0x has compiler-level support for std::is_same. So I was thinking if a generalization like below is also supported in the compiler.

template <typename... Args, typename What>
struct is_present
{
  enum { value = (What in Args...)? 1 : 0 };
};
+1  A: 

No, you have to use (partial) specialization with variadic templates to do compile-time computations like this:

#include <type_traits>

template < typename Tp, typename... List >
struct contains : std::true_type {};

template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
    std::true_type,
    contains<Tp, Rest...>
>::type {};

template < typename Tp >
struct contains<Tp> : std::false_type {};

There is only one other intrinsic operation for variadic templates and that is the special form of the sizeof operator which computes the length of the parameter list e.g.:

template < typename... Types >
struct typelist_len
{
   const static size_t value = sizeof...(Types);
};

Where are you getting "it has serious compilation-time overhead" with boost mpl from? I hope you are not just making assumptions here. Boost mpl uses techniques such as lazy template instantiation to try and reduce compile-times instead of exploding like naive template meta-programming does.

snk_kid
May be a larger set of intrinsic operations should be supported by the language/compiler than just sizeof.... (4 dots!) IMO, checking existence is as "fundamental" as finding the size. I feel mpl has performance overhead based on this test that I wrote. http://www.dre.vanderbilt.edu/~sutambe/files/mpl_intersection.cpp I'm using hand-coded Intersection algorithm as well as MPL's version. g++ 4.4 takes same time to compile both. Variadic templates version compiles 10 times faster. BTW, can you please suggest me some reading on mpl's lazy template instantiation technique?
Sumant
I found some good examples of lazy evaluation in the C++ Template Metaprogramming book. Isn't that obvious?! Thanks anyways.
Sumant
Yep all you have to do is try to avoid template instantiation of a meta-functions (by exposing nested type alias type") before giving the result to another boost meta-functions. Boost meta-functions have been designed to evaluate meta-functions at very last moment the nested type alias is needed. You should also try and avoid bare values and use the meta-data type wrappers (like mpl::bool_) because they are designed to work lazily as well.Sometimes boost mpl provides two forms of a meta-function, try to use the one that promotes lazy instantiation.
snk_kid
I feel this a more approachable answer because of its simplicity!
Sumant
I would prefer using techniques of specialization to match it up: `template<typename M, typename ...L> struct C : std::false_type {}; template<typename M, typename ...L> struct C<M, M, L...> : std::true_type {}; template<typename M, typename L1, typename ...L> struct C<M, L1, L...> : C<M, L...> {};`
Johannes Schaub - litb
A: 

If you want to avoid manual type recursion, std::common_type appears to me to be the only utility in the STL which is a variadic template, and hence the only one which could potentially encapsulate recursion.


Solution 1

std::common_type finds the least-derived type in a set of types. If we identify numbers with types, specifically high numbers with less-derived types, it finds the greatest number in a set. Then, we have to map equality to the key type onto a level of derivation.

using namespace std;

struct base_one { enum { value = 1 }; };
struct derived_zero : base_one { enum { value = 0 }; };

template< typename A, typename B >
struct type_equal {
 typedef derived_zero type;
};

template< typename A >
struct type_equal< A, A > {
 typedef base_one type;
};

template< typename Key, typename ... Types >
struct pack_any {
 enum { value =
     common_type< typename type_equal< Key, Types >::type ... >::type::value };
};


Solution 2

We can hack common_type a little more. The standard says

A program may specialize this trait if at least one template parameter in the specialization is a user-defined type.

and describes exactly what is inside it: a recursive partial specialization case, a case which applies a binary operator, and a terminal case. Essentially, it's a generic zip function, and you can add whatever binary operation you please. Here I used addition because it's more informative than OR. Note that is_same returns an integral_constant.

template< typename Addend >
struct type_sum { // need to define a dummy type to turn common_type into a sum
    typedef Addend type;
};

namespace std { // allowed to specialize this particular template
template< typename LHS, typename RHS >
struct common_type< type_sum< LHS >, type_sum< RHS > > {
    typedef type_sum< integral_constant< int,
     LHS::type::value + RHS::type::value > > type; // <= addition here
};
}

template< typename Key, typename ... Types >
struct pack_count : integral_constant< int,
 common_type< type_sum< is_same< Key, Types > > ... >::type::type::value > {};
Potatoswatter
This one is quite a brain teaser. But I liked it! Assumes good understanding of common_type trait. I had to dig it in the C++0x public draft. Combining that with implicit conversion to base_one via type_equal is clever. C++ has just too many of these clever tricks. Can something more intuitive be done using std::is_same and logical-or somehow?
Sumant
@Sumant: Yep :v) .
Potatoswatter
That's somewhat better but still mind-numbing details must be internalized.
Sumant