Template-defined number of template parameters (very meta)

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.