Search code examples
c++templatesc++14c++17template-templates

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


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?


Solution

  • 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.