2

I have a few classes:

template <int,char>
class Foo{};
template <int,char>
class Bar{};

And I want to get all combinations with a few arguments, like this:

// {1, 2}, {'a', 'b'}
using CartesianProduct = mp_list<Foo<1,'a'>, Foo<1,'b'>,...,Bar<2,'b'>>;

I can change template parameters to types and use std::integral_constant, if it cannot be done with numeric constants.

I found a similar question, but it's far more difficult. Creating all template permutations with MPL. I assume, there is a better and easier solution for my case.

Igor Pugachev
  • 604
  • 1
  • 5
  • 14
  • Will there always be the same kinds of parameters in your template classes? I.e. can we rely on there only being `template ` as the example? – Caleth May 25 '21 at 16:05
  • @Caleth yes, parameters are the same in every template class. – Igor Pugachev May 25 '21 at 16:17
  • Why do you want this, though? More specifically, isn't MPL a pre-C++11 library, superseded by later efforts (and sometimes by straightforward compile-time functions)? – einpoklum May 25 '21 at 22:41
  • @einpoklum By your logic, boost::mp11 shouldn't exist. You still cannot create and modify types with non-templated constexpr functions. I need it for some tests. – Igor Pugachev May 26 '21 at 06:49
  • @ИгорьПугачев: Is mp_list part of mp11 or the original mpl? – einpoklum May 26 '21 at 06:55

3 Answers3

2

For fun I gave it a try to make a manual implementation of this work with variadic templates, std::integer_sequence and std::tuple_cat and was actually surprised to get it to work quite easily: Based on simple arrays of arbitrary length

constexpr std::array<int,2>  t1 = {1, 2};
constexpr std::array<char,3> t2 = {'a', 'b', 'c'};

and given the variadic template classes (e.g. Foo and Bar) it generates all possible permutations itself and merges them to an std::tuple (or boost::mpl::list) data type for which a convenient alias can be defined:

using SomeAlias = typename AllClassesAllPermutations<t1.size(), t2.size(), t1, t2, Foo, Bar>::type;
  • Not being familiar with Boost::MPL I tried to stay away from it: My first version is actually based on std::tuple

    Try std::tuple version here!

  • If you want to have a boost::mpl::list instead of an std::tuple this can be easily done with another variadic template conversion function

    Try boost::mpl::list version here!

The next section will go into more detail on how this can be achieved!


For this purpose I wrote a class that - based on a few template arguments such as the said combinations of the int and char parameters as well as the corresponding template template class - creates an std::tuple containing all permutations of the int and char arrays of this single template template class. This is done by creating two permutation vectors holding the pairwise permutations. E.g. two input arrays t1_in = {1, 2} and t2_in = {'a', 'b', 'c'}; are expanded to t1 = {1, 1, 1, 2, 2, 2} and t2 = {'a', 'b', 'c', 'a', 'b', 'c'} with the function duplicateArray and then an std::integer_sequence is created so the two can be fused into a template T<t1[I], t2[I]> that in combination with the std::integer_sequence gives you all permutations for a single class:

template <std::size_t I1, std::size_t I2, std::array<int,I1> const& t1_in, std::array<char,I2> const& t2_in, template <int, char> class T>
class SingleClassAllPermutations {
  private:    
    template <std::size_t K, std::size_t I, typename TA, std::size_t J>
    static constexpr auto duplicateArray(std::array<TA, J> const& arr_in) {
      std::array<TA, I*J*K> arr_out {0};
      std::size_t l {0};
      for (std::size_t i = 0; i < I; ++i) {
        for (std::size_t j = 0; j < J; ++j) {
          for (std::size_t k = 0; k < K; ++k) {
            arr_out[l] = arr_in[j];
            ++l;
          }
        }
      }
      return arr_out;
    }

    static constexpr std::size_t N = I1*I2;
    static constexpr std::array<int,N>  t1 = duplicateArray<I2,1>(t1_in);
    static constexpr std::array<char,N> t2 = duplicateArray<1,I1>(t2_in);
    static constexpr auto index_seq = std::make_index_sequence<N>{};

    template <std::size_t... I>
    static constexpr auto getTuple(std::index_sequence<I...>) {
      return std::make_tuple(T<t1[I], t2[I]>() ...);
    }

  public:
    static constexpr auto tup = getTuple(index_seq);
};

Then I created a variadic template which may take several different template template classes (as long as their template arguments are int and char respectively) as additional input arguments and then merges the internal tuple tup of the individual permutations with std::tuple_cat to create the tuple containing all possible permutations of the int and char arrays as well as the different variadic template template classes:

template <std::size_t I1, std::size_t I2, std::array<int,I1> const& t1, std::array<char,I2> const& t2, template <int,char> class... Ts>
class AllClassesAllPermutations {
  public:
    static constexpr auto getTuple() {
      return std::tuple_cat(SingleClassAllPermutations<I1,I2,t1,t2,Ts>::tup ...);
    }

    using type = decltype(getTuple());
};

Now one can then define global constexpr arrays inside namespaces and a convenient alias such as

namespace some_namespace {
  constexpr std::array<int,2>  t1 = {1, 2};
  constexpr std::array<char,3> t2 = {'a', 'b', 'c'};
  using SomeInstantiation = typename AllClassesAllPermutations<t1.size(), t2.size(), t1, t2, Foo, Bar>::type;
}

This way one can re-use the templates above to generate different data types.

The templates then expand it to all possible permutations, in the case above to

std::tuple<Foo<1,'a'>, Foo<1,'b'>, Foo<1,'c'>, Foo<2,'a'>, Foo<2,'b'>, Foo<2,'c'>,
           Bar<1,'a'>, Bar<1,'b'>, Bar<1,'c'>, Bar<2,'a'>, Bar<2,'b'>, Bar<2,'c'>>

Finally if you want to have an boost::mpl::list instead you can introduce a conversion function such as

template <class... Ts>
static constexpr auto tupleToMplList(std::tuple<Ts...>) {
  return boost::mpl::list<Ts...>{};
}

that you again use to decltype instead of the std::tuple which results in a data type

boost::mpl::list<Foo<1,'a'>, Foo<1,'b'>, Foo<1,'c'>, Foo<2,'a'>, Foo<2,'b'>, Foo<2,'c'>,
                 Bar<1,'a'>, Bar<1,'b'>, Bar<1,'c'>, Bar<2,'a'>, Bar<2,'b'>, Bar<2,'c'>>

Try it here

2b-t
  • 2,414
  • 1
  • 10
  • 18
  • Good and simple solution, thank you! It's not scalable to N template arguments, but it's okay for me. And there is `mp_rename` for a tuple conversion to another template. – Igor Pugachev May 26 '21 at 06:41
  • @ИгорьПугачев You are welcome! Glad you like it. You should be able to scale it to `N` template arguments. Already the version above takes **arbitrary many input classes** as the classes are variadic and you can **initialise the `std::array` with `constexpr` functions to arbitrary length**: [Add e.g. this](https://wandbox.org/permlink/VYzoSpb2XaS7uuyd). If you know what the template arguments precisely look like you can put the code I have put in the namespace inside another class with template parameters for the lower and upper bounds of the arrays and simply call that template then. – 2b-t May 26 '21 at 07:26
  • The problem with being able to vary the number of classes, the `int` as well as the `char` is that you can't have variadic templates with multiple (e.g. three such as in this case) parameter packs that can't be deducted from an argument. What you can do is create an `std::integer_sequence`, pass that one as the input argument to a function (similar to what I have done above with the references to an array). In that case you will end up in a two stage process like above, so one class which will handle the instantiation of a single class and another one that merges them. – 2b-t May 26 '21 at 07:30
  • The reason for that is that (at least from what I know) you can't use two different-size parameter packs inside the same function. So I actually I am not sure if a library like `Boost::MPL` can even do that. I guess with such a library you can only have one variadic dimension so to speak. But feel free to keep the question open and wait for a better answer using `Boost::MPL`. In the meanwhile I guess my solution should do the job! Best of luck! – 2b-t May 26 '21 at 07:33
  • 1
    So, I found MPL solution by myself, even with numbers. It was easier than I thought. But thank for your help! – Igor Pugachev May 26 '21 at 08:40
  • @ИгорьПугачев Interesting. Wouldn't have imagined that! I will have a look at the solution you posted! Thank you! – 2b-t May 26 '21 at 08:58
2

Finally, I figured it out by myself.

using namespace boost::mp11;

template <typename C1, typename C2>
struct Foo{};

template <typename C1, typename C2>
struct Bar{};

template <template <typename...> typename... F>
using mp_list_q = mp_list<mp_quote<F>...>;

using TypeList = mp_product<mp_invoke_q, 
    mp_list_q<Foo, Bar>, 
    mp_list_c<int, 1, 2>, 
    mp_list_c<char, 'a', 'b'>>;

Result:

boost::mp11::mp_list<
Foo<std::integral_constant<int, 1>, std::integral_constant<char, (char)97> >,
Foo<std::integral_constant<int, 1>, std::integral_constant<char, (char)98> >,
Foo<std::integral_constant<int, 2>, std::integral_constant<char, (char)97> >,
Foo<std::integral_constant<int, 2>, std::integral_constant<char, (char)98> >,
Bar<std::integral_constant<int, 1>, std::integral_constant<char, (char)97> >,
Bar<std::integral_constant<int, 1>, std::integral_constant<char, (char)98> >,
Bar<std::integral_constant<int, 2>, std::integral_constant<char, (char)97> >,
Bar<std::integral_constant<int, 2>, std::integral_constant<char, (char)98> > >

It uses std::integral_constant for arguments, but it's simple and short. Try it here

UPD: And I found how to use integers itselfs!

template <int, char>
struct Foo{};

template <int, char>
struct Bar{};

template <template <auto...> typename F>
struct mp_quote_c
{
    template <typename... C>
    using fn = F<C::value...>;
};

template <template <auto...> typename... F>
using mp_list_qc = mp_list<mp_quote_c<F>...>;

using TypeList = mp_product<mp_invoke_q,
    mp_list_qc<Foo, Bar>,
    mp_list_c<int, 1, 2>, 
    mp_list_c<char, 'a', 'b'>>;

Result:

boost::mp11::mp_list<
Foo<1, (char)97>, Foo<1, (char)98>, Foo<2, (char)97>, Foo<2, (char)98>,
Bar<1, (char)97>, Bar<1, (char)98>, Bar<2, (char)97>, Bar<2, (char)98> >

Try it here!

UPD2: Only clang can compile this code. It seems like there is a bug in msvc and gcc, because clang can build this code even with -pedantic-errors

UPD3: Now gcc also can compile it

Igor Pugachev
  • 604
  • 1
  • 5
  • 14
  • 1
    Nice! I will actually keep it in mind in case I will have to do something similar some day. Way easier than my solution. Thanks for sharing! – 2b-t May 26 '21 at 09:01
0

There is an implementation called combine_view here: https://stackoverflow.com/a/27175631/225186 that I used in my library https://gitlab.com/correaa/boost-covariant

Maybe nowadays there is something better in the MP11 library.

This is my distilled version:

#include<boost/mpl/vector.hpp>
#include<boost/mpl/set.hpp>

#include<boost/mpl/fold.hpp>
#include<boost/mpl/zip_view.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/pop_front.hpp>

namespace boost{
namespace mpl{

template <class Seq, class ItrSeq>
class SequenceCombiner{

template < class _Seq = mpl::vector<int_<1> > >
struct StateSeq{
        typedef typename pop_front<_Seq>::type sequence;
        typedef typename mpl::at<_Seq, int_<0> >::type state;
        typedef _Seq type;
};

template < class _Seq, class _State >
struct set_state{
        typedef StateSeq< typename push_front<_Seq, _State>::type > type;
};

struct NextOp {

template<typename Out, typename In, typename Enable = typename Out::state>
class apply{
        using seq = typename Out::sequence;
        using new_state = typename Out::state;
        using in_seq = typename mpl::at<In,int_<0> >::type;
        using in_itr = typename mpl::at<In,int_<1> >::type;

        using new_seq = typename mpl::push_back<seq, in_itr>::type;
public:
        typedef typename set_state<new_seq, int_<0> >::type type;
};

template<typename Out, typename In>
class apply<Out,In,mpl::int_<1> >{
        typedef typename Out::sequence seq;
        typedef typename Out::state state;
        typedef typename mpl::at<In,int_<0> >::type in_seq;
        typedef typename mpl::at<In,int_<1> >::type in_itr;

        typedef typename mpl::begin<in_seq>::type Itr_begin;
        typedef typename mpl::next<in_itr>::type  Itr_next;
        typedef typename mpl::end<in_seq>::type   Itr_end;

typedef typename mpl::if_<
        boost::is_same<Itr_next,Itr_end>,
        typename mpl::push_back<seq,Itr_begin>::type,
        typename mpl::push_back<seq,Itr_next>::type
>::type new_seq;

typedef typename mpl::if_<boost::is_same<Itr_next,Itr_end>,
        mpl::int_<1>,
        mpl::int_<0>
>::type new_state;

public:
        typedef typename set_state<new_seq, new_state>::type type;

};
};

typedef typename mpl::fold<
        typename mpl::zip_view<mpl::vector<Seq, ItrSeq> >::type,
        StateSeq<>,
        NextOp
>::type StateResult;

public:

typedef typename mpl::if_<
        boost::is_same<typename StateResult::state, int_<1> >,
        typename mpl::transform<Seq, mpl::end<_1> >::type,
        typename StateResult::sequence
>::type next;

};

template<typename Seq, typename Itrs>
struct combine_iterator{
        typedef mpl::forward_iterator_tag category;
        typedef Seq  seq;
        typedef typename transform<Itrs, deref<_1> >::type type;
};

template <class Seq, class Pos>
struct next<typename mpl::combine_iterator<Seq, Pos>>{
    typedef typename mpl::SequenceCombiner<Seq,Pos>::next next_Pos;
    typedef boost::mpl::combine_iterator<Seq, next_Pos> type;
};

template<class Seq>
class combine_view{
        using Pos_begin = typename mpl::transform<Seq, mpl::begin<_1> >::type;
        using Pos_end   = typename mpl::transform<Seq, mpl::end<_1>   >::type;
public:
        using begin = combine_iterator<Seq, Pos_begin>;
        using end   = combine_iterator<Seq, Pos_end>;
        using type  = combine_view;
};

} // mpl
} // boost

Example use:

using boost::mpl::combine_view;
using product = combine_view<
    boost::mpl::list<
        boost::mpl::list<double, int, std::string>, 
        boost::mpl::list<double, int>,
        boost::mpl::list<std::string, char>
    >
>::type;               

static_assert( boost::mpl::size<product>::value == 12 );
alfC
  • 14,261
  • 4
  • 67
  • 118
  • I tried mp11::mp_product, but template template parameter is only one. I've seen a few similar questions where a placeholder was used in similar cases, but I still haven't figured out how to use it here. – Igor Pugachev May 25 '21 at 17:17
  • @ИгорьПугачев, look here for an example: http://soulrace.top:3001/boost/mp11/src/commit/2d709e5639e92c5ba84d99bad740bfeb06fd6a4f/test/mp_product.cpp#L44 – alfC May 25 '21 at 18:00
  • It's not quite what I need. I want to use multiple templates and types, but mp_product use only one template (first argument). And, as I understand, your combine_view also can't combine templates and types, as in the question. I found it, but this case is far more difficult. - https://www.py4u.net/discuss/97058. I assume, there is more easier solution for me. – Igor Pugachev May 25 '21 at 18:51
  • @ИгорьПугачев, better link, https://stackoverflow.com/questions/5908961/creating-all-template-permutations-with-mpl . Yes, the problem of combining types and values is problematic due to limitations of the language (they don't fit the `T...` pattern). What I recommend to begin with is to wrap your constants in a type to start. (for example `std::integral_constant`) or MPL's `char_` etc. – alfC May 25 '21 at 19:22
  • As I said, I can replace constants with types, it's not the problem. But I can't combine multiple templates. I wrote `mp_product, Bar<_1>>, mp_transform>>` and got what I need, but I don't know how to scale this to N arguments for templates (Foo, Bar). I think this solution is just a bad crutch. – Igor Pugachev May 25 '21 at 19:59
  • I think it will be more clear what you want if you don't use elipsis `...` in the desired outcome `using CartesianProduct = mp_list, Foo<1,'b'>,...,Bar<2,'b'>>;` Please complete the `...` explicitly. Finally, sorry that this didn't help, I think the piece that you are missing is that `template template classes` can be also represented by pure types with an internal typedef. – alfC May 25 '21 at 20:23