Why can't non-type template parameters be dependent on parameter of specialization?

Viewed 49

I've got this code:

#include <iostream>

template <typename num_t, num_t num>
struct factorial {
    consteval operator num_t() { return num * factorial<num_t, num - 1>{}; }
};

template <typename num_t>
struct factorial<num_t, 2> {
    consteval operator char() { return 2; }
};

int main() {
    std::cout << factorial<unsigned long long, 20>{} << '\n';
}

Clang gives me an error under 2 in struct factorial<num_t, 2>. The message is:

error: non-type template argument specializes a template parameter with dependent type 'num_t'

I've looked around a bit and it seems to be because of this part in the standard:

Paragraph [temp.class.spec] 14.5.5/8 of the standard:

The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization. [ Example:

   template <class T, T t> struct C {};
   template <class T> struct C<T, 1>; // error

   template< int X, int (*array_ptr)[X] > class A {};
   int array[5];
   template< int X > class A<X,&array> { }; // error

—end example ]

Can someone explain the reasoning behind this section of the standard. It seems to me that they're simply getting in my way. What practical (or even theoretical) value comes with this rule?

Also, I'm more than a little confused about the wording that's being used. Can someone who understands maybe translate it into a more easy-to-understand phrasing?

1 Answers

This is a quick way to fix it:

template <typename num_t, num_t num, typename = void>
struct factorial {
    constexpr operator num_t() { return num * factorial<num_t, num - 1>{}; }
};

template <typename num_t, num_t num>
struct factorial<num_t, num, std::enable_if_t<num == 2>> {
    constexpr operator char() { return 2; }
};

The reason is (likely), you can't construct num_t num without being aware of what num_t is; thus, compiler cannot match the 2.

The paragraph basically says, template arguments are matched orthogonally, i.e., without co-dependency - this likely makes matching quicker in the compiler.

Related