Reputation: 40811
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?
Upvotes: 3
Views: 215
Reputation: 72311
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 ofP
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 typeT[N]
isstd::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.
Upvotes: 1