Template-defined number of template parameters (very meta)

Viewed 345
template<int Degree> using curve = // some definition
template<int MaxDegree> using curve_variant = std::variant<curve<1>, curve<2>, .. curve<MaxDegree>>;

In the above example, the number of parameters passed to std::variant's template would change depending on curve_variant's parameters: For example, curve_variant<4> would resolve to std::variant<curve<1>, curve<2>, curve<3>, curve<4>>.

Since MaxDegree is known at compile time, this feels possible. But I also don't have a clue how to begin implementing this.

4 Answers

With std::integer_sequence helper, you might do:

template <typename Seq> struct curve_variant_impl;

template <int ... Is>
struct curve_variant_impl<std::integer_sequence<int, Is...>>
{
    using type = std::variant<curve<1 + Is>...>;
}; 

template <int MaxDegree>
using curve_variant = typename curve_variant_impl<std::make_integer_sequence<int, MaxDegree>>::type;

As the other answers show std::integer_sequence is a nice tool. Suppose we didn't have it.

The following is only to illustrate what code we would have to write if we didn't have std::integer_sequence. As a matter of fact, there is no reason to write it this way, if you do not have C++14, you can reimplement is easily.

#include <variant>
#include <type_traits>


template<int Degree> struct curve{};

// helper to add a type to a variant
template <typename A,typename... others>
struct merge_variants {
    using type = std::variant<others...,A>;
};

template <typename A,typename... others>
struct merge_variants<A,std::variant<others...>> : merge_variants<A,others...> {};

// the recursion:
template <int MaxDegree>
struct Foo {
    using type = typename merge_variants< curve<MaxDegree>,typename Foo<MaxDegree-1>::type >::type;
};

// the base case:
template <>
struct Foo<1> {
    using type = std::variant< curve<1> >;
};


int main() {
    static_assert(std::is_same<std::variant<curve<1>,curve<2>,curve<3>> , Foo<3>::type >::value);
}

Recursion is rather expensive, to instantiate Foo<N> (sorry for the name) N other types have to be instantiated, even though we never asked for them. std::integer_sequence can avoid the recursion completely.

#include <utility>
#include <variant>
template<int Degree>
struct curve{};

template<typename index_seq>
struct curve_variant_impl;

template<int...indices>
// Start binding indices from 1, not zero
struct curve_variant_impl<std::integer_sequence<int,0,indices...>>{
    using type = std::variant<curve<indices>...>;
};


template<int MaxDegree>
//make_integer_sequence makes [0,MaxDegree), we want [1,MaxDegree]
using curve_variant = typename curve_variant_impl<std::make_integer_sequence<int,MaxDegree+1>>::type;
int main() {
   static_assert(std::is_same_v<curve_variant<4>,std::variant<curve<1>, curve<2>, curve<3>, curve<4>>>);
}

The above works only with non-negative values, so you might as well use std::size_t which is natural type for indices.

Just to go with the other answers, if you have C++11 but not C++14, you can emulate std::integer_sequence with a clever trick:

template <int...>
struct seq { };

template <int N, int... S>
struct gens : gens<N - 1, N - 1, S...> { };

template <int... S>
struct gens<0, S...> {
    typedef seq<S...> type;
};

With this pattern, gens operates like integer_sequence does. gens<N>::type is seq<1, 2, 3, ..., N>. Thus, you use it just the same

template <typename T>
struct make_curve_variant_impl

template <int... N>
struct make_curve_variant_impl<seq<N...>>
{
    typef std::variant<curve<N+1>...> type;
};

template <typename N>
struct make_curve_variant
{
    typedef make_curve_variant_impl<typename gens<N>::type>:: type;
};
Related