18

I'd like to create the cross product of a list of types using variadic templates.

Here's what I have so far:

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

template<typename...> struct type_list {};

template<typename T1, typename T2> struct type_pair {};

template<typename T, typename... Rest>
  struct row
{
  typedef type_list<type_pair<T,Rest>...> type;
};

template<typename... T>
  struct cross_product
{
  typedef type_list<typename row<T,T...>::type...> type;
};

int main()
{
  int s;
  typedef cross_product<int, float, short>::type result;
  std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl;

  return 0;
}

This program outputs:

$ g++ -std=c++0x cross_product.cpp ; ./a.out 
type_list<type_list<type_pair<int, int>, type_pair<int, float>, type_pair<int, short> >, type_list<type_pair<float, int>, type_pair<float, float>, type_pair<float, short> >, type_list<type_pair<short, int>, type_pair<short, float>, type_pair<short, short> > >

But I'd like it to output:

type_list<type_pair<int,int>, type_pair<int,float>, type_pair<int,short>, type_pair<float,int>,...>

That is, without the nested type_lists.

Is there a direct way to do this without the row helper, or should the solution "unwrap" the nested type_lists somehow?

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Jared Hoberock
  • 11,118
  • 3
  • 40
  • 76
  • `__cxa_demangle` calls malloc underneath, so you are responsible to [free the memory](http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html). – Jesse Good Feb 03 '12 at 00:25
  • 4
    We all know where that question is from. And *you* know it was given to us as homework by Andrei. :) – Xeo Feb 03 '12 at 01:08
  • One of the solutions below work, but I don't know if it would be as concise and elegant as Andrei's "margin quote" intended. – keveman Feb 03 '12 at 01:29
  • @R.MartinhoFernandes Did Andrei give him homework personally or on any blog , link? – Mr.Anubis Feb 03 '12 at 18:49
  • @Mr.Anubis it's from his GoingNative talk. – R. Martinho Fernandes Feb 03 '12 at 19:07
  • A link for those who'd like to see where this question came from :) http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Variadic-Templates-are-Funadic – Brett Rossier Feb 01 '13 at 22:22

9 Answers9

12

A nice clean version I think:

cross_product.cpp:

#include "type_printer.hpp"

#include <iostream>

template<typename ...Ts> struct type_list {};
template<typename T1, typename T2> struct pair {};

// Concatenation
template <typename ... T> struct concat;
template <typename ... Ts, typename ... Us>
struct concat<type_list<Ts...>, type_list<Us...>>
{
    typedef type_list<Ts..., Us...> type;
};

// Cross Product
template <typename T, typename U> struct cross_product;

// Partially specialise the empty case for the first type_list.
template <typename ...Us>
struct cross_product<type_list<>, type_list<Us...>> {
    typedef type_list<> type;
};

// The general case for two type_lists. Process:
// 1. Expand out the head of the first type_list with the full second type_list.
// 2. Recurse the tail of the first type_list.
// 3. Concatenate the two type_lists.
template <typename T, typename ...Ts, typename ...Us>
struct cross_product<type_list<T, Ts...>, type_list<Us...>> {
    typedef typename concat<
        type_list<pair<T, Us>...>,
        typename cross_product<type_list<Ts...>, type_list<Us...>>::type
    >::type type;
};

struct A {};
struct B {};
struct C {};
struct D {};
struct E {};
struct F {};

template <typename T, typename U>
void test()
{
    std::cout << print_type<T>() << " \u2a2f " << print_type<U>() << " = "
        << print_type<typename cross_product<T, U>::type>() << std::endl;
}

int main()
{
    std::cout << "Cartesian product of type lists\n";
    test<type_list<>, type_list<>>();
    test<type_list<>, type_list<A>>();
    test<type_list<>, type_list<A, B>>();
    test<type_list<A, B>, type_list<>>();
    test<type_list<A>, type_list<B>>();
    test<type_list<A>, type_list<B, C, D>>();
    test<type_list<A, B>, type_list<B, C, D>>();
    test<type_list<A, B, C>, type_list<D>>();
    test<type_list<A, B, C>, type_list<D, E, F>>();
    return 0;
}

type_printer.hpp:

#ifndef TYPE_PRINTER_HPP
#define TYPE_PRINTER_HPP

#include "detail/type_printer_detail.hpp"

template <typename T>
std::string print_type()
{
    return detail::type_printer<T>()();
}

#endif

detail/type_printer_detail.hpp:

#ifndef DETAIL__TYPE_PRINTER_DETAIL_HPP
#define DETAIL__TYPE_PRINTER_DETAIL_HPP

#include <typeinfo>
#include <cxxabi.h>
#include <string>

template <typename ...Ts> struct type_list;
template <typename T1, typename T2> struct pair;

namespace detail {

// print scalar types
template <typename T>
struct type_printer {
    std::string operator()() const {
        int s;
        return abi::__cxa_demangle(typeid(T).name(), 0, 0, &s);
    }   
};

// print pair<T, U> types
template <typename T, typename U>
struct type_printer<pair<T, U>> {
    std::string operator()() const {
        return "(" + type_printer<T>()() + "," + type_printer<U>()() + ")";
    }   
};

// print type_list<T>
template <>
struct type_printer<type_list<>> {
    std::string operator()() const {
        return "\u2205";
    }   
};

template <typename T>
struct type_printer<type_list<T>> {
    std::string operator()() const {
        return "{" + type_printer<T>()() + "}";
    }   
    std::string operator()(const std::string& sep) const {
        return sep + type_printer<T>()();
    }   
};

template <typename T, typename ...Ts>
struct type_printer<type_list<T, Ts...>> {
    std::string operator()() const {
        return "{" + type_printer<T>()() + type_printer<type_list<Ts...>>()(std::string(", ")) + "}";
    }   
    std::string operator()(const std::string& sep) const {
        return sep + type_printer<T>()() + type_printer<type_list<Ts...>>()(sep);
    }   
};
}

#endif

Run:

g++ -std=c++0x cross_product.cpp && ./a.out

Output:

Cartesian product of type lists
∅ ⨯ ∅ = ∅
∅ ⨯ {A} = ∅
∅ ⨯ {A, B} = ∅
{A, B} ⨯ ∅ = ∅
{A} ⨯ {B} = {(A,B)}
{A} ⨯ {B, C, D} = {(A,B), (A,C), (A,D)}
{A, B} ⨯ {B, C, D} = {(A,B), (A,C), (A,D), (B,B), (B,C), (B,D)}
{A, B, C} ⨯ {D} = {(A,D), (B,D), (C,D)}
{A, B, C} ⨯ {D, E, F} = {(A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F)}

(I noticed on Windows using Chrome that the cross product unicode character is not coming out well. Sorry, I don't know how to fix that.)

Shane
  • 757
  • 3
  • 11
8

Somehow my brain is fried: I think I'm using more code than is needed but, at least, it has the desired results (although I didn't fix the memory leak):

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

template<typename...> struct type_list {};

template<typename T1, typename T2> struct type_pair {};

template<typename T, typename... Rest>
  struct row
{
  typedef type_list<type_pair<T,Rest>...> type;
};

template <typename... T> struct concat;
template <typename... S, typename... T>
struct concat<type_list<S...>, type_list<T...>>
{
    typedef type_list<S..., T...> type;
};

template <typename... T>
struct expand
{
    typedef type_list<T...> type;
};
template <> struct expand<> { typedef type_list<> type; };
template <typename... T, typename... L>
struct expand<type_list<T...>, L...>
{
    typedef typename concat<typename expand<T...>::type, typename expand<L...>::type>::type type;
};

template<typename... T>
  struct cross_product
{
    typedef typename expand<type_list<typename row<T,T...>::type...>>::type type;

};

int main()
{
  int s;
  typedef cross_product<int, float, short>::type result;
  std::cout << abi::__cxa_demangle(typeid(result).name(), 0, 0, &s) << std::endl;

  return 0;
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
4

C++17

Working Demo

Logic to concatenate type_lists to avoid nested type_list like you are asking for:

// base case: 2 type_lists
template<class... Ts, class... Us>
auto concat(type_list<Ts...>, type_list<Us...>) -> type_list<Ts..., Us...>;

// recursive case: more than 2 type_lists
template<class... Ts, class... Us, class... Rest>
auto concat(type_list<Ts...>, type_list<Us...>, Rest...) -> decltype(concat(type_list<Ts..., Us...>{}, Rest{}...));

Note that these functions don't have (or need) implementations; this is a trick to avoid class template specialization (I learned it from Hana Dusikova's compile time regular expressions)

Then, simplifying your row and cross_product impls as pairs and cross_product_impl, respectively:

template<class T, class... Ts>
using pairs = type_list<type_pair<T, Ts>...>;

template<class... T>
auto cross_product_impl()
{
    if constexpr(sizeof...(T) == 0)
        return type_list<> {};
    if constexpr(sizeof...(T) == 1)
        return type_list<type_pair<T, T>...>{};
    if constexpr(sizeof...(T) > 1)
        return concat(pairs<T, T...>{}...);
}

if constexpr allows us to more easily express the logic, I think.

Finally a type alias for cross_product that gives us what the type would be if we theoretically invoked cross_product_impl:

template<class... T>
using cross_product = decltype(cross_product_impl<T...>());

Usage basically the same as before:

cross_product<int, float, short> result;
Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
4

Maybe something like this:

template <typename ...Args> struct typelist { };

template <typename S, typename T> struct typelist_cat;

template <typename ...Ss, typename ...Ts>
struct typelist_cat<typelist<Ss...>, typelist<Ts...>>
{
    typedef typelist<Ss..., Ts...> type;
};


template <typename S, typename T> struct product;

template <typename S, typename ...Ss, typename ...Ts>
struct product<typelist<S, Ss...>, typelist<Ts...>>
{
    // the cartesian product of {S} and {Ts...}
    // is a list of pairs -- here: a typelist of 2-element typelists
    typedef typelist<typelist<S, Ts>...> S_cross_Ts;

    // the cartesian product of {Ss...} and {Ts...} (computed recursively)
    typedef typename product<typelist<Ss...>, typelist<Ts...>>::type
        Ss_cross_Ts;

    // concatenate both products
    typedef typename typelist_cat<S_cross_Ts, Ss_cross_Ts>::type type;
};

// end the recursion
template <typename ...Ts>
struct product<typelist<>, typelist<Ts...>>
{
    typedef typelist<> type;
};

Now you should be able to use product<typelist<A,B,C>, typelist<D,E,F>>::type.

dyp
  • 38,334
  • 13
  • 112
  • 177
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • `typename T` not defined for the 3rd and 4th templates. – keveman Feb 03 '12 at 00:38
  • Doesn't compile. It doesn't use type pairs to pair up types for forming the cartesian product, so I don't know how it works. – keveman Feb 03 '12 at 19:18
  • @keveman: Right, there was another error, and that's fixed now. Turns out a concatenator is needed, which I had forgotten about the first time round. It's a product in the same sense that 2 times 3 is 6, and not {(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)}. If the OP actually *wants* pairs, he should say so. – Kerrek SB Feb 03 '12 at 19:50
  • @KerrekS: I dont know if the title of this question has been changed, but the op did ask for [the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product), which implies pairs – cheshirekow Sep 20 '12 at 22:03
  • @cheshirekow, the cartesian power of a cartesian product is not limited to the square, i.e. the results are not necessarily pairs. – Patrick Fromberg Oct 24 '13 at 18:34
  • @KerrekSB I know this is rather old, but are you sure it's working as intended? See [this live example](http://coliru.stacked-crooked.com/a/df51a8a9fcbd4450), and try the out-commented version. – dyp Oct 27 '13 at 21:03
  • @DyP: I can't say I actually understand what I was doing; please do feel free to fix it, or let me know if it should be deleted. – Kerrek SB Oct 27 '13 at 23:26
  • I think I fixed it now, also added support for empty lists as first parameter to `product`. [Live example with three test cases](http://coliru.stacked-crooked.com/a/e47af43dbd7ebba7) – dyp Oct 27 '13 at 23:40
3

So far all solutions have drawbacks, unnecessary dependencies, unnecessary helpers and all are restricted to the Cartesian power of two. The following solution has no such drawbacks and supports:

  1. Any cartesian power including 0.
  2. Returning the empty set if any of the factors is an empty set.
  3. The code is self contained and does not depend on any include files.
  4. The inputs of the function can be of any template type.
  5. The type of the output list can be specified via the first template parameter.

It was actually to harder to implement (but good as homework) then I thought. I am actually thinking about creating a little generator which allows me an extended template syntax which makes these things really easy.

Simplified the code works as follows: product converts an input list tuple<A...>,tuple<B...>,tuple<C...> into tuple<tuple<A>...>, tuple<B...>, tuple<C...>. This second list is then passed to product_helper which does the recursive Cartesian product computation.

template <typename... T> struct cat2;

template <template<typename...> class R, typename... As, typename... Bs>
struct cat2 <R<As...>, R<Bs...> > {
        using type = R <As..., Bs...>;
};

template <typename... Ts> struct product_helper;

template <template<typename...> class R, typename... Ts>
struct product_helper < R<Ts...> > { // stop condition
        using type = R< Ts...>;
};

template <template<typename...> class R, typename... Ts>
struct product_helper < R<R<> >, Ts... > { // catches first empty tuple
        using type = R<>;
};

template <template<typename...> class R, typename... Ts, typename... Rests>
struct product_helper < R<Ts...>, R<>, Rests... > { // catches any empty tuple except first
        using type = R<>;
};

template <template<typename...> class R, typename... X, typename H, typename... Rests>
struct product_helper < R<X...>, R<H>, Rests... > {
        using type1 = R <typename cat2<X,R<H> >::type...>;
        using type  = typename product_helper<type1, Rests...>::type;
};

template <template<typename...> class R, typename... X, template<typename...> class Head, typename T, typename... Ts, typename... Rests>
struct product_helper < R<X...>, Head<T, Ts...>, Rests... > {
        using type1 = R <typename cat2<X,R<T> >::type...>;
        using type2 = typename product_helper<R<X...> , R<Ts...> >::type;
        using type3 = typename cat2<type1,type2>::type;
        using type  = typename product_helper<type3, Rests...>::type;
};

template <template<typename...> class R, typename... Ts> struct product;

template <template<typename...> class R>
struct product < R > { // no input, R specifies the return type
    using type = R<>;
};

template <template<typename...> class R, template<typename...> class Head, typename... Ts, typename... Tail>
struct product <R, Head<Ts...>, Tail... > { // R is the return type, Head<A...> is the first input list
    using type = typename product_helper< R<R<Ts>...>, Tail... >::type;
};

Here is a compilable example of how the code can be used.

Patrick Fromberg
  • 1,313
  • 11
  • 37
  • 1
    It seems to me as if you could write `cat2` as `template struct cat2; template< template class R, class... A, class... B> struct, R> { .. };` – dyp Oct 27 '13 at 15:14
2

Here's another solution.

#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

template <typename ...Args> struct typelist { };
template <typename, typename> struct typepair { };

template <typename S, typename T> struct product;
template <typename S, typename T> struct append;

template<typename ...Ss, typename ...Ts>
struct append<typelist<Ss...>, typelist<Ts...>> {
  typedef typelist<Ss..., Ts...> type;
};

template<>
struct product<typelist<>, typelist<>> {
  typedef typelist<> type;
};

template<typename ...Ts>
struct product<typelist<>, typelist<Ts...>> {
  typedef typelist<> type;
};

template<typename ...Ts>
struct product<typelist<Ts...>, typelist<>> {
  typedef typelist<> type;
};

template<typename S, typename T, typename ...Ss, typename ...Ts>
struct product<typelist<S, Ss...>, typelist<T, Ts...>> {
  typedef typename
          append<typelist<typepair<S, T>,
                          typepair<S, Ts>...,
                          typepair<Ss, T>...>,
        typename product<typelist<Ss...>, typelist<Ts...>>::type>::type type;
};

int main(void)
{
  int s;
  std::cout << abi::__cxa_demangle(
  typeid(product<typelist<int, float>, typelist<short, double>>::type).name(), 0, 0, &s)     << "\n";
  return 0;
}
keveman
  • 8,427
  • 1
  • 38
  • 46
1

Note: This is NOT what the OP asked for... but may be of relevance to others (like me) who stumble upon this question. Here is how it can be done using a Loki::TypeList (i.e. prior C++-11), perhaps of historical interest or for compatability sake.

Also, perhaps it is presumptuous of me to pollute loki's namespace. YMMV.

crossproduct.h

#include "loki/NullType.h"
#include "loki/Typelist.h"

namespace Loki {
namespace   TL {

/// a pair of two types
template <typename A_t, typename B_t>
struct TypePair
{
    typedef A_t A;
    typedef B_t B;
};


/// a template which takes one type and pairs it with all other types
/// in another typelist
template <class T, class TList > struct DistributePair;

/// specialization of Distribute for the nulltype
template < class TList >
struct DistributePair< NullType, TList >
{
     typedef NullType type;
};


/// specialization of Distribute where the second parameter is nulltype
template <class T >
struct DistributePair< T, NullType >
{
     typedef NullType type;
};

/// specialization of Distribute where the first parameter is a
/// typelist
template <class T, class Head, class Tail >
struct DistributePair< T, Typelist<Head,Tail> >
{
     typedef Typelist<
                 TypePair<T,Head>,
                 typename DistributePair<T,Tail>::type
                     > type;
};

/// performs cartesion product of two typelists
template <class TListA, class TListB> struct CrossProduct;

/// specialization of CrossProduct for NullType
template <class TListB>
struct CrossProduct< NullType, TListB >
{
    typedef NullType type;
};

/// specialization of CrossProduct for recursion
template <class Head, class Tail, class TListB>
struct CrossProduct< Typelist<Head,Tail>, TListB >
{
    typedef typename Append<
            typename DistributePair< Head,TListB >::type,
            typename CrossProduct< Tail, TListB >::type
              >::Result type;
};

} // namespace TL
} // namespace Loki

test.cpp

#include <crossproduct.h>
#include <loki/HierarchyGenerators.h>
#include <iostream>

struct A{};
struct B{};
struct C{};

struct D{};
struct E{};
struct F{};

typedef LOKI_TYPELIST_3(A,B,C)  TypeListA_t;
typedef LOKI_TYPELIST_3(D,E,F)  TypeListB_t;

typedef typename Loki::TL::CrossProduct< TypeListA_t, TypeListB_t >::type Cross_t;

template <typename T> const char* toString();

template <> const char* toString<A>(){ return "A"; };
template <> const char* toString<B>(){ return "B"; };
template <> const char* toString<C>(){ return "C"; };
template <> const char* toString<D>(){ return "D"; };
template <> const char* toString<E>(){ return "E"; };
template <> const char* toString<F>(){ return "F"; };

template <typename T> struct Printer
{
    Printer()
    {
        std::cout << toString<T>() << ", ";
    }
};

template <typename T1, typename T2>
struct Printer< Loki::TL::TypePair<T1,T2> >
{
    Printer()
    {
        std::cout << "(" << toString<T1>() << "," << toString<T2>() << "), ";
    }
};


typedef Loki::GenScatterHierarchy< TypeListA_t, Printer > PrinterA_t;
typedef Loki::GenScatterHierarchy< TypeListB_t, Printer > PrinterB_t;
typedef Loki::GenScatterHierarchy< Cross_t, Printer >     PrinterCross_t;

int main(int argc, char** argv)
{
    std::cout << "\nType list A: \n   ";
    PrinterA_t a;
    std::cout << "\nType list B: \n   ";
    PrinterB_t b;
    std::cout << "\nType list Cross: \n   ";
    PrinterCross_t cross;

    return 0;
}

output

Type list A: 
   A, B, C, 
Type list B: 
   D, E, F, 
Type list Cross: 
   (A,D), (A,E), (A,F), (B,D), (B,E), (B,F), (C,D), (C,E), (C,F), 
cheshirekow
  • 4,797
  • 6
  • 43
  • 47
1

With Boost.Mp11, this is a short one-liner (as always):

using input = type_list<int, float, short>;
using result = mp_product<
    type_pair,
    input, input>;

Demo.


We can generalize this to picking N things, with repetition, from that input. We can't use type_pair anymore to group our elements, so we'll just have a list of type_list of elements. To do that, we basically need to write:

mp_product<type_list, input, input, ..., input>
//                    ~~~~~~~ N times ~~~~~~~~

Which is also the same as:

mp_product_q<mp_quote<type_list>, input, input, ..., input>
//                                ~~~~~~~ N times ~~~~~~~~

One way to do that is:

template <int N>
using product = mp_apply<
    mp_product_q,
    mp_append<
        mp_list<mp_quote<type_list>>, 
        mp_repeat_c<mp_list<input>, N>
        >>;

Demo.

Barry
  • 286,269
  • 29
  • 621
  • 977
0

Really enjoyed this "homework" assignment :)

Both solutions below create a class full of type_list typedefs, along with member functions that will check to see if a given list of types exist in the class as a type_list.

The first solution creates all possible combinations of types from 1 to N types per type_list (the width parameter defines N). The second solution creates only pairs of types.

First Solution

template<typename... Ts> struct type_list { typedef type_list<Ts...> type; };

template<size_t, typename...> struct xprod_tlist_ {};

template<typename... Ts, typename... Us>
struct xprod_tlist_<1, type_list<Ts...>, Us...> {};

template<size_t width, typename... Ts, typename... Us>
struct xprod_tlist_<width, type_list<Ts...>, Us...>
: type_list<Ts..., Us>...
, xprod_tlist_<width - 1, type_list<Ts..., Us>, Us...>... {};

template<size_t width, typename... Ts> struct xprod_tlist
: type_list<Ts>..., xprod_tlist_<width, type_list<Ts>, Ts...>... {
    template<typename... Us> struct exists
    : std::is_base_of<type_list<Us...>, xprod_tlist<width, Ts...>> {};

    template<typename... Us> struct assert_exists {
        static_assert(exists<Us...>::value, "Type not present in list");
    };
};

Usage:

typedef xprod_tlist<5, int, char, string, float, double, long> X;

//these pass
X::assert_exists<int, int, int, int, int> assert_test1;
X::assert_exists<double, float, char, int, string> assert_test2;

//these fail
X::assert_exists<char, char, char, char, char, char> assert_test3;
X::assert_exists<int, bool> assert_test4;

//true
auto test1 = X::exists<int, int, int, int, int>::value;
auto test2 = X::exists<double, float, char, int, string>::value;

//false
auto test3 = X::exists<char, char, char, char, char, char>::value;
auto test4 = X::exists<int, bool>::value;

Second Solution

template<class T, class U> struct type_pair { typedef type_pair<T, U> type; };
template<class... Ts> struct type_list {};
template<class...> struct xprod_tlist_ {};

template<class T, class... Ts, class... Us>
struct xprod_tlist_<type_list<T, Ts...>, type_list<Us...>>
: type_pair<T, Us>..., xprod_tlist_<type_list<Ts...>, type_list<Us...>> {};

template<class... Ts>
struct xprod_tlist : xprod_tlist_<type_list<Ts...>, type_list<Ts...>> {
    template<class T, class U> struct exists
    : std::is_base_of<type_pair<T, U>, xprod_tlist<Ts...>> {};

    template<class T, class U> struct assert_exists {
        static_assert(exists<T, U>::value, "Type not present in list");
    };
};

Usage:

typedef xprod_tlist<int, float, string> X;

//these pass
X::assert_exists<int, int> assert_test1;
X::assert_exists<int, float> assert_test2;
X::assert_exists<int, string> assert_test3;
X::assert_exists<float, int> assert_test4;
X::assert_exists<float, float> assert_test5;
X::assert_exists<float, string> assert_test6;
X::assert_exists<string, int> assert_test7;
X::assert_exists<string, float> assert_test8;
X::assert_exists<string, string> assert_test9;

//this fails
X::assert_exists<int, char> assert_test10;

//true
auto test1 = X::exists<int, int>::value;
auto test2 = X::exists<int, float>::value;
auto test3 = X::exists<int, string>::value;
auto test4 = X::exists<float, int>::value;
auto test5 = X::exists<float, float>::value;
auto test6 = X::exists<float, string>::value;
auto test7 = X::exists<string, int>::value;
auto test8 = X::exists<string, float>::value;
auto test9 = X::exists<string, string>::value;

//false
auto test10 = X::exists<int, char>::value;
Brett Rossier
  • 3,420
  • 3
  • 27
  • 36