Type of non-type parameter in template template class is non deducible in C++14 but deducible in C++17
Asked Answered
H

1

3

The title is a bit confusing but what I mean is this specific case:

template<class>
struct get_type_of_nontype;

template<class T, T Value, template<T> class Template>
struct get_type_of_nontype<Template<Value>> {
    using type = T;
};

So I can use it like this:

#include <type_traits>

template<int I>
class int_non_type {};

static_assert(
    std::is_same<typename get_type_of_nontype<int_non_type<0>>::type, int>::value,
    "T is deduced to be `int` as `template<T> class Template` is `template<int> class int_non_type`"
);

This works fine in C++17. In C++14 I get the following errors:

gcc 8:

<source>:5:8: error: template parameters not deducible in partial specialization:
struct get_type_of_nontype<Template<Value>> {
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:5:8: note:         'T'

clang 7:

<source>:5:8: error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used [-Wunusable-partial-specialization]
struct get_type_of_nontype<Template<Value>> {
       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:4:16: note: non-deducible template parameter 'T'
template<class T, T Value, template<T> class Template>
               ^

And then they both complain that struct get_type_of_nontype<int_non_type<0>> is incomplete, so typename get_type_of_non_type<int_non_type<0>>::type can't compile.

Why is this different between C++14 and C++17? Is this just a compiler bug? If not, is there a way to do this in C++14?

Hysterectomy answered 6/3, 2019 at 23:11 Comment(0)
N
1

The Standard wording in [temp.deduct.type] paragraphs 13 and 14 changed. So yes, your example is invalid in C++14, but is allowed in C++17 thanks to a new language feature.

C++14:

A template type argument cannot be deduced from the type of a non-type template-argument.

[Example:

template<class T, T i> void f(double a[10][i]);
int v[10][20];
f(v);           // error: argument for template-parameter T cannot be deduced

-- end example]

C++17:

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value. [Example:

template<long n> struct A { };

template<typename T> struct C;
template<typename T, T n> struct C<A<n>> {
  using Q = T;
};

using R = long;
using R = C<A<2>>::Q;    // OK; T was deduced to long from the
                         // template argument value in the type A<2>

-- end example] The type of N in the type T[N] is std::size_t. [Example:

template<typename T> struct S;
template<typename T, T n> struct S<int[n]> {
  using Q = T;
};

using V = decltype(sizeof 0);
using V = S<int[42]>::Q;  // OK; T was deduced to std::size_t from the type int[42]

-- end example]

[Example:

template<class T, T i> void f(int (&a)[i]);
int v[10];
void g() {
  f(v);         // OK: T is std::size_t
}

-- end example]

This seems a bit related to another C++17 template change: C++17 is the first version to allow placeholder types in non-type template parameters, as in template <auto Value> or template <auto* Ptr>. I'd expect compiler implementations would need some similar logic for supporting both of the two language features.

Northey answered 6/3, 2019 at 23:38 Comment(2)
Oh so T is deduced from T Value and not template <T> class. This makes it sound like there is no way to do this at all in C++14.Hysterectomy
Probably not. Unless maybe you only need to support a finite list of types like the arithmetic types, in which case you could write a specialization for each one.Northey

© 2022 - 2024 — McMap. All rights reserved.