5

According to the reference, the name of a non-type template parameter is optional, even when assigning a default value (see (1) and (2)). Therefore these template structs are valid:

template <int> struct Foo {};
template <unsigned long = 42> struct Bar {};

I haven't seen a possibility of accessing the values of the non-type parameters. My question is: What's the point of unnamed/anonymous non-type template parameters? Why are the names optional?

andreee
  • 4,459
  • 22
  • 42
  • 1
    Why differentiate non-type template parameters from type template parameters? – Jarod42 Jan 20 '20 at 14:04
  • 1
    You seem to insist that this question is specifically about non-type template parameters, but the answer will probably apply equally to type parameters. Are you familiar with applications for nameless type parameters? – François Andrieux Jan 20 '20 at 14:04
  • @Jarod42 Admittedly I haven't thought much about the reasons for unnamed type template parameters, but I definitely see no reason for the case of non-type template parameters. Actually I was wondering if there's a difference between non-type/type templates, but I wanted to be more specific in my question. – andreee Jan 20 '20 at 14:06
  • Notice that applies also to regular parameter `void foo(int = 42)` – Jarod42 Jan 20 '20 at 14:06
  • @Jarod42 your last comment actually just extends the scope of my question :-D – andreee Jan 20 '20 at 14:10
  • 1
    @François you will see unnamed type template parameters much more frequently for SFINAE. Doing this with non-type template parameters seems more complicated or impossible to me. – n314159 Jan 20 '20 at 14:19
  • @n314159 Actually type template parameter would be more common when using SFINAE in class templates, while non-type template parameters would be the better and more common choice for a function template and SFINAE. – super Jan 20 '20 at 16:10
  • @super Are you sure about that? I hadn't seen a use of non-type template parameter SFINAE before today. Especially `std::enable_if_t` makes it type-based SFINAE pretty common. – n314159 Jan 20 '20 at 16:12
  • @n314159 Yes. Using non-type parameters is the only way to overload template functions and choose between them based on SFINAE if you want to put your SFINAE code in the template part of the function (not in the function signature part). – super Jan 20 '20 at 16:14
  • @n314159 Or maybe saying that it's "better" is a bit opinion based. But in my experience it's at least more common and in my opinion better. :-) – super Jan 20 '20 at 16:16
  • @n314159 Did you check the output? `Type, no t.length()` for both `S` and `R`. You miss-spelled `length`. If corrected you get `call to type is ambiguous` clearly illustrating my point. [corrected version](https://godbolt.org/z/mSZQPf) – super Jan 20 '20 at 17:40
  • @n314159 Actually, your `non_type` is also broken in a similar fashion. The `size` method needs to be `constexpr` for the template to be valid. If fixed that too gives the same ambiguous error. – super Jan 20 '20 at 17:57
  • @super Yes, sorry, I did not check that thouroughly enough. Do you maybe have a reference on what is the difference between type and non-type template parameters that is the reason for SFINAE working with one but not the other? – n314159 Jan 23 '20 at 09:30
  • 1
    @n314159 Not a good reference for that specifically. The reason is that a default value is not part of the template signature. With a non-type we can get around that by specifying the parameter-type with an `enable_if` leading to either substitution failure or a valid non-type parameter as shown in [this answer](https://stackoverflow.com/questions/36958312/c-function-template-overload-on-template-parameter). – super Jan 23 '20 at 09:48

4 Answers4

8

First, we can split declaration from definition. So name in declaration is not really helpful. and name might be used in definition

template <int> struct Foo;
template <unsigned long = 42> struct Bar;

template <int N> struct Foo {/*..*/};
template <unsigned long N> struct Bar {/*..*/};

Specialization is a special case of definition.

Then name can be unused, so we might omit it:

template <std::size_t, typename T>
using always_t = T;

template <std::size_t ... Is, typename T>
struct MyArray<std::index_sequence<Is...>, T>
{
    MyArray(always_t<Is, const T&>... v) : /*..*/
};

or used for SFINAE

template <typename T, std::size_t = T::size()>
struct some_sized_type;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
6

What's the point of unnamed/anonymous non-type template parameters?

I can think of specialization:

template<int = 42>
struct Foo{
   char x;
};

template<>
struct Foo<0> {
   int x;
};

template<>
struct Foo<1> {
   long x;
};

Then:

Foo<0> a; // x data member is int
Foo<1> b; // x data member is long
Foo<7> c; // x data member is char
Foo<>  d; // x data member is char
JFMR
  • 23,265
  • 4
  • 52
  • 76
2

Oh, you can access them!

template <int> struct Foo {};

template <int N>
int get(Foo<N>) {
    return N;
}

int main() {
    Foo<3> foo;
    return get(foo);
}

This might be a bit contrived. But in general for some templates you don't want to name them and then it is convenient that you don't have to.

Tobi
  • 2,591
  • 15
  • 34
  • 1
    Hmm... of course I meant that accessing the unnamed parameter _from within_ the template isn't possible. If I define a constant expression before template instantiation sure I can always access that value :-) `constexpr int N = 42; Foo f;` - but that doesn't help much... – andreee Jan 20 '20 at 14:16
1

Unamed type and non-type parameters also allow you to delay type instanciation, using template-template parameters.

Take destination_type in the function below for instance.
It can resolve to any type that has a template-type parameter, and 0 to N template-values parameters.

template <template <typename, auto...> typename destination_type, typename TupleType>
constexpr auto repack(TupleType && tuple_value)
{
    return [&tuple_value]<std::size_t ... indexes>(std::index_sequence<indexes...>) {
        return destination_type{std::get<indexes>(tuple_value)...};
    }(std::make_index_sequence<std::tuple_size_v<TupleType>>{});
}
static_assert(repack<std::array>(std::tuple{1,2,3}) == std::array{1,2,3});

Such mechanic comes handy when you need an abstraction on parameters-pack.

Here, for instance, we do not care if Ts... is a parameter-pack containing multiple arguments, or expand to a single type which itself has multiples template parameters.

-> Which can be transposed from template-type parameters to template-value parameters.

See gcl::mp::type_traits::pack_arguments_as_t

Complete example available on godbolt here.

template <template <typename ...> class T, typename ... Ts>
class pack_arguments_as {
    template <template <typename...> class PackType, typename... PackArgs>
    constexpr static auto impl(PackType<PackArgs...>)
    {
        return T<PackArgs...>{};
    }
    template <typename... PackArgs>
    constexpr static auto impl(PackArgs...)
    {
        return T<PackArgs...>{};
    }
    public:
    using type = decltype(impl(std::declval<Ts>()...));
};
template <template <typename ...> class T, typename ... Ts>
using pack_arguments_as_t = typename pack_arguments_as<T, Ts...>::type;

namespace tests
{
    template <typename... Ts>
    struct pack_type {};

    using toto = pack_arguments_as_t<std::tuple, pack_type<int, double, float>>;
    using titi = pack_arguments_as_t<std::tuple, int, double, float>;

    static_assert(std::is_same_v<toto, titi>);
    static_assert(std::is_same_v<toto, std::tuple<int, double, float>>);
    static_assert(std::is_same_v<pack_type<int, double, float>, pack_arguments_as_t<pack_type, toto>>);
}
Guss
  • 762
  • 4
  • 20