What's the point of unnamed non-type template parameters?
Asked Answered
O

4

7

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?

Oro answered 20/1, 2020 at 14:1 Comment(14)
Why differentiate non-type template parameters from type template parameters?Childhood
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?Whitener
@Childhood 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.Oro
Notice that applies also to regular parameter void foo(int = 42)Childhood
@Childhood your last comment actually just extends the scope of my question :-DOro
@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.Theresita
@Theresita 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.Mauritamauritania
@Mauritamauritania 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.Theresita
@Theresita 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).Mauritamauritania
@Theresita 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. :-)Mauritamauritania
@Theresita 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 versionMauritamauritania
@Theresita 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.Mauritamauritania
@Mauritamauritania 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?Theresita
@Theresita 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.Mauritamauritania
C
9

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;
Childhood answered 20/1, 2020 at 14:19 Comment(0)
B
7

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
Banyan answered 20/1, 2020 at 14:6 Comment(0)
S
3

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.

Sundown answered 20/1, 2020 at 14:12 Comment(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<N> f; - but that doesn't help much...Oro
G
2

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>>);
}
Geosynclinal answered 2/3, 2021 at 13:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.