Garo
Garo

Reputation: 111

Definition and Initialization of a tuple whose components are of the same templated class, but with different specialisations

I am new to c++ metaprogramming. I tried to look at other answers, but I was not able to find one that could suit my problem. Or simply I was not able to apply it to my case. Here I will post a simplified version of the code, to highlight the main features which I would like to obtain.

What I would like to achieve, is the construction of a std::tuple of dimension N (N known at compile-time), whose components type is given by a template class, MyType, depending on two parameters M and N. M is fixed, while the type of the tuple component i is actually MyType<M,i>, for i=0,...,N. Since I have to define recursively a tuple of these types, I have considered the DefineType template.

// general definition
template<Integer M, Integer N>
struct DefineType
{
    using rest = typename DefineType<M, N-1>::type;
    using type = decltype(std::tuple_cat(std::declval< std::tuple< MyType<M,N>>>(),
                                         std::declval<rest>() ));
};

// specialization for N=0
template<Integer M>
struct DefineType<M,0>
{
 using type = typename std::tuple< MyType<M,0> >;
};

This should produce the following types:

DefineType< M, N=0 >: std::tuple< MyType< M,0 > > ;

DefineType< M, N=1 >: std::tuple< MyType< M,0 >, MyType< M,1 > > ;

DefineType< M, N=2 >: std::tuple< MyType< M,0 >, MyType< M,1 > , MyType< M,2 > > ;

and so on, up to a general N.

Then I would like also to initialize a tuple of this kind, based on something which I call param of type Param. For doing this, I write a code of this kind:

// general definition
template<Integer M, Integer N>
typename DefineType<M,N>::type  MyClass(Param param)
{
    return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                  MyClass<M,N-1>(param)   )   ;
}
// specialization for N=0
template<Integer M>
typename DefineType<M,0>::type MyClass(Param param)
{
    return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
}

Finally in the main:

int main()
{

// M and N given
const auto myobject=MyClass<M,N>(param);

}

The code is not compiling, complaining that I am initializing too many times DefineType<M,N>. Basically N does not reach the base-case, with N=0. I do not get why...So for sure the recursive type definition is wrong. But, in addition to this, maybe there are other errors that I do not see. I hope you can help me in understanding how to do this. I apologize, but meta programming is very new (and difficult) to me.

Thank you.

Upvotes: 1

Views: 79

Answers (2)

max66
max66

Reputation: 66210

I see two problems in your code.

(1) you say that you want that

DefineType< M, N=2 > is std::tuple< MyType< M,0 >, MyType< M,1 > , MyType< M,2 > >

but writing

using type = decltype(std::tuple_cat(std::declval< std::tuple< MyType<M,N>>>(),
                                     std::declval<rest>() ));

inside DefineType, you get the opposite order; you obtain that

DefineType< M, N=2 > is std::tuple<MyType<M, 2>, MyType<M, 1> , MyType<M, 0>>

If you want the order from zero to N, you have to define, in DefineType, before the rest and then the N element; I mean

using type = decltype(std::tuple_cat(
      std::declval<rest>(),
      std::declval<std::tuple<MyType<M,N>>>() ));

(2) The recursion for MyClass() function doesn't works because in your recursive version call the same MyClass() ever with two template parameters

template<Integer M, Integer N>
typename DefineType<M,N>::type  MyClass(Param param)
{
    return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                  MyClass<M,N-1>(param)   )   ;
} // you call the second parameter .........^^^
  // also when N is 1 (and N-1 is 0)

so the base-case (defined with only one template parameter) never matches.

Unfortunately partial template specialization doesn't works for template functions, so you can use partial template specialization of structs (see aschepler's answer) or, if you prefer, SFINAE to enable/disable the two versions of MyClass() according the value of N.

I propose the following solution

// specialization for N == 0
template <Integer M, Integer N>
typename std::enable_if<(N == 0), typename DefineType<M,0>::type>::type
   MyClass(Param param)
 { return std::tuple<MyType<M, 0>>(MyType<M, 0>(param)); }

// general definition
template <Integer M, Integer N>
typename std::enable_if<(N > 0u), typename DefineType<M,N>::type>::type
   MyClass(Param param)
{
    return std::tuple_cat(
       MyClass<M,N-1>(param),
       std::tuple<MyType<M,N>>(MyType<M,N>(param)) );
}

Observe that now the ground case (N == 0) has two template parameter but is enabled only when N is zero. The other case in enabled only when N > 0.

Observe also that you have to write before the ground case version because it is used by the recursive version.

Observe also that I've switched the order of rest/actual type.

If you can use C++14, so std::make_index_sequence/std::index_sequence, I strongly suggest to avoid recursion and to follow the aschepler's suggestion.

You can also avoid recursion for the DefineType itself using specialization as follows

template <Integer, Integer N, typename = std::make_index_sequence<N+1u>>
struct DefineType;

template <Integer M, Integer N, std::size_t ... Is>
struct DefineType<M, N, std::index_sequence<Is...>>
 { using type = std::tuple<MyType<M, Is>...>; };

The following is a full compiling C++14 example

#include <tuple>
#include <type_traits>

using Integer = std::size_t;
using Param = int;

template <Integer M, Integer N>
struct MyType
 { MyType (Param) {} };

template <Integer, Integer N, typename = std::make_index_sequence<N+1u>>
struct DefineType;

template <Integer M, Integer N, std::size_t ... Is>
struct DefineType<M, N, std::index_sequence<Is...>>
 { using type = std::tuple<MyType<M, Is>...>; };

template <Integer M, Integer N>
std::enable_if_t<(N == 0), typename DefineType<M,0>::type>
   MyClass(Param param)
 { return std::tuple<MyType<M, 0>>(MyType<M, 0>(param)); }

// general definition
template <Integer M, Integer N>
std::enable_if_t<(N > 0u), typename DefineType<M,N>::type>
   MyClass(Param param)
{
    return std::tuple_cat(
       MyClass<M,N-1>(param),
       std::tuple<MyType<M,N>>(MyType<M,N>(param)) );
}

int main ()
 {
   using t0 = typename DefineType<42u, 0u>::type;
   using u0 = std::tuple<MyType<42u, 0u>>;
   using t1 = typename DefineType<42u, 1u>::type;
   using u1 = std::tuple<MyType<42u, 0u>, MyType<42u, 1u>>;
   using t2 = typename DefineType<42u, 2u>::type;
   using u2 = std::tuple<MyType<42u, 0u>, MyType<42u, 1u>, MyType<42u, 2u>>;

   static_assert( std::is_same<t0, u0>::value, "!" );
   static_assert( std::is_same<t1, u1>::value, "!" );
   static_assert( std::is_same<t2, u2>::value, "!" );

   auto const myobject = MyClass<42u, 2u>(12); 
 }

Upvotes: 1

aschepler
aschepler

Reputation: 72366

Given the definitions

template<Integer M, Integer N>
typename DefineType<M,N>::type  MyClass(Param param)
{
    return std::tuple_cat(std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                                  MyClass<M,N-1>(param)   )   ;
}
template<Integer M>
typename DefineType<M,0>::type MyClass(Param param)
{
    return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
}

what you have is two overloaded distinct function templates. The second is not a "partial specialization" of the first because there is no such thing a function template partial specialization, only class template specializations. (And so the call MyClass<M,N-1>(param) can't possibly match the second template, even if it had been previously declared, since the second one only accepts one template argument, meaning the first template is infinitely recursive.)

One solution could be to use a helper class template:

namespace MyClass_detail {
    template<Integer M, Integer N>
    struct helper {
        static typename DefineType<M,N>::type build(const Param& param)
        {
            return std::tuple_cat(
                std::tuple<MyType<M,N>>(MyType<M,N>(param)), 
                MyClass<M,N-1>(param));
        }
    };

    template<Integer M>
    struct helper<M, 0> {
        static typename DefineType<M,0>::type build(const Param& param)
        {
            return std::tuple<MyType<M, 0>>(MyType<M, 0>(param));
        }
    };
}

template<Integer M, Integer N>
typename DefineType<M,N>::type MyClass(Param param)
{
    return MyClass_detail::helper<M,N>::build(Param);
}

Though I would recommend taking advantage of std::make_integer_sequence. (This is a C++14 feature, and I see your question is tagged C++11. If you can't use C++14 or later, a search should turn up some replacement implementations for make_integer_sequence and related tools that can be used in C++11.)

#include <utility>
#include <tuple>

namespace MyClass_detail {
    template<Integer M, Integer N, Integer ...Inds>
    auto MyClass_helper(const Param &param, std::integer_sequence<Integer, Inds...>)
    {
        return std::make_tuple(MyType<M, N-Inds>(param)...);
    }
}

template<Integer M, Integer N>
auto MyClass(Param param)
{
    return MyClass_detail::MyClass_helper<M,N>(
        param, std::make_integer_sequence<Integer, N+1>{});
}

// And if DefineType is wanted for other uses:
template<Integer M, Integer N>
using DefineType = decltype(MyClass<M,N>(std::declval<Param>()));

See the full working demo on coliru.

Upvotes: 2

Related Questions