14

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 };
};
plasmacel
  • 8,183
  • 7
  • 53
  • 101
Sumant
  • 4,286
  • 1
  • 23
  • 31

4 Answers4

26

Fortunately, the C++ standard has evolved. With C++1z aka C++17, you can finally iterate easily over parameter packs. So the code for the answer is (almost) as simple, as suggested in the question:

template<typename What, typename ... Args>
struct is_present {
    static constexpr bool value {(std::is_same_v<What, Args> || ...)};
};

The weird-looking (std::is_same_v<What, Args> || ...) is expanded by the compiler internally to (std::is_same_v<What, Args[0]> || std::is_same_v<What, Args[1]> || ...), which is exactly, what you want. It even correctly yields false with an empty Args parameter pack.

It is even possible to do the whole check inline in a function or method - no helper structs are required anymore:

template<typename T, typename ... List>
void foo(T t, List ... lst)
{
    if constexpr((std::is_same_v<T, List> || ...)) {
        std::cout << "T is in List" << std::endl;
    } else {
        std::cout << "T is not in List" << std::endl;
    }
}

Note: This has been taken from another question, that was marked as a duplicate of this question. As this is the "canonical" question for this topic, I added that important information here.

Kai Petzke
  • 2,150
  • 21
  • 29
  • 2
    Nice solution. It can be simplified a bit: `template constexpr inline bool is_present_v = (std::is_same_v || ...);` – Vladislav Aug 04 '21 at 07:42
7

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
  • 3,457
  • 3
  • 23
  • 18
  • 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 Jan 23 '10 at 07:17
  • I found some good examples of lazy evaluation in the C++ Template Metaprogramming book. Isn't that obvious?! Thanks anyways. – Sumant Jan 23 '10 at 07:25
  • 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 Jan 23 '10 at 10:10
  • I feel this a more approachable answer because of its simplicity! – Sumant Jan 23 '10 at 23:28
  • 5
    I would prefer using techniques of specialization to match it up: `template struct C : std::false_type {}; template struct C : std::true_type {}; template struct C : C {};` – Johannes Schaub - litb Mar 05 '10 at 14:32
2

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 fold 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
  • 134,909
  • 25
  • 265
  • 421
  • 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 Jan 23 '10 at 20:05
  • That's somewhat better but still mind-numbing details must be internalized. – Sumant Jan 23 '10 at 23:16
  • @Alexandre: Thanks. Actually I've been doing some functional programming and was just wondering exactly that! I couldn't remember because Python calls it `reduce` and C++ calls it `accumulate`. – Potatoswatter Jun 16 '11 at 07:46
  • @Potatoswatter: zip takes two lists and returns a list of pairs (hence the "zip" name). – Alexandre C. Jun 16 '11 at 08:32
2

Since C++17, you can inherit from std::disjunction with a normal pack expansion of std::is_same<What, Args>... which performs a logical OR between all the std::is_sames. Your is_present type trait will have a static constexpr bool value member variable holding the result. This is different from a fold expression over || since it's short-circuiting the instantiation of the rest of the is_same<>::values if a match is found. You can see how that works in the C++11/14 implementation below.

#include <type_traits>

template<class What, class... Args>
struct is_present : std::disjunction<std::is_same<What, Args>...> {};

Using C++11 or C++14, you can define your own disjunction using std::conditional and use that type trait instead of std::disjunction when defining is_present:

template<class...> struct disjunction : std::false_type {};
template<class T> struct disjunction<T> : T {};
template<class T, class... Ts>
struct disjunction<T, Ts...> :
    std::conditional<bool(T::value),
                     T, // no `is_same<>::value` instantiations for the Ts...
                     disjunction<Ts...>>::type {};

Since C++14 you can also create a helper variable template:

template<class... Ts>
constexpr bool is_present_v = is_present<Ts...>::value;
int main() {
    std::cout << is_present_v<int, double, int> << '\n'; // prints 1
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108