The Problem
Considering the following (C 20) code
#include <array>
// class template depends on array of ints
template<std::size_t N, std::array<int, N> arr>
struct S {};
// fix array length (for convenience)
template<int x>
using U = S<1, std::array{x}>;
// function template depends on array element only
template<int x>
auto func(U<x>) {}
int main() {
func(U<2>{}); // error: compiler fails to deduce `x = 2`
}
Why does the compiler not deduce x = 2
for the call of func
?
Expectation
Since using std::array
s as template arguments is possible and comparison of them is constexpr
I am surprised that this does not compile.
For any hints regarding why this seemingly simple deduction is not possible for the compilers I would be grateful!
Context
The following variations do compile but are in my opinion less desirable and I want to avoid them if possible:
Variation 1
One variation is to provide an explicit template parameter as in
func<2>(U<2>{})
But this is not convenient and leads to repetition.
Variation 2
Another possible variation is to carry the explicit template argument around and then restrict the template as in
template<std::size_t N, std::array<int, N> arr>
auto func(S<N, arr>) requires (N == 1) {}
This has the disadvantage to being very verbose.
CodePudding user response:
A non-type template argument containing a template parameter in a subexpression (rather than as the full argument) is a non-deduced context. (See [temp.deduct.type]/5.3.)
In other words when deducing the function argument of type S<1, std::array{2}>
against the function parameter type S<1, std::array{x}>
, then std::array{x}
is an undeduced context because x
, a template parameter, appears as a subexpression.
So std::array{x}
can't be used to deduce the template argument for x
.
The reason for this rule is that it is generally impossible to match std::array{2}
against std::array{x}
.
When the matching is done the compiler doesn't have the expression std::array{2}
anymore. It just has an object of type std::array<int, 1>
created from that expression and is supposed to figure out which value x
needs to be given to the initializer to make an object with the same value (in the structural equality sense used for template argument identity). We know because of how std::array
is defined that this requires x
to be 2
, but in general for other types any argument to the constructor could produce objects of any value. It is impossible to formulate a general rule to perform this matching.
I am not exactly sure what your intention with the function is, but if you are just trying to make sure that the function is callable only if N == 1
then
template<auto arr>
auto func(S<1, arr>) {}
will do. Now the non-type template argument to S
is the template parameter arr
as a whole, not as a subexpression. Now the compiler doesn't have to figure out for which values of arr
the expression evaluates to match the value of the corresponding non-type template argument in the function argument. It can just assume that arr
is exactly equal to it.