Deduction of templated non-type template parameter

140 Views Asked by At

Should the compiler be able to deduce the type of the non-type template parameter of function template f that is defaulted to n{}? Which specific rule in the C++20 standard allows/mandates this?

template<typename = int>
struct s {
    template<typename = int>
    struct n {};

    template<n = n{}>
    constexpr int f();
};
static_assert(requires { s{}.f(); }); // clang ok, gcc ok, msvc nope

Surprisingly, MSVC for some reason accepts the following code while the other compilers won't even allow the syntax. Is MSVC simply having a tea party with its imaginary friends here? And, is there a workaround that works for all 3 compilers, other than changing the type of the NTTP of f from n to auto?

template<typename = int>
struct s {
    template<typename = int>
    struct n {};

    template<n<typename> = n{}>
    constexpr int f();
};
static_assert(requires { s{}.f(); }); // clang nope, gcc nope, msvc ok

Live example


Regarding the workaround, one way to go about it is to add a member-type to n and use a requires-clause to do a manual type-constraint check. This is not the type of workaround I'm looking for, it adds too much bloat and is simply too cumbersome. Also, having to add explicit constness to the types just to evade some other compiler discrepancy isn't particularly appealing to me either.

#include <concepts>

template<typename = int>
struct s {
    template<typename T = int>
    struct n { using type = T; };

    template<auto N = n{}>
    requires std::same_as<decltype(N) const,
        n<typename decltype(N)::type> const>
    constexpr int f();
};

The error produced by MSVC:

<source>(9): error C2672: 's<int>::f': no matching overloaded function found
<source>(6): note: could be 'int s<int>::f(void)'
<source>(9): note: 'int s<int>::f(void)': could not deduce template argument
for '__formal'
<source>(9): error C2607: static assertion failed
1

There are 1 best solutions below

0
Brian Bi On

C++20 [temp.arg.nontype]/1 permits class template argument deduction for template parameters:

If the type T of a template-parameter (13.2) contains a placeholder type (9.2.9.6) or a placeholder for a deduced class type (9.2.9.7), the type of the parameter is the type deduced for the variable x in the invented declaration

T x = template-argument ;

If a deduced parameter type is not permitted for a template-parameter declaration (13.2), the program is ill-formed.

In the template-parameter n = n{}, the two ns are "placeholders for a deduced class type". The type of the template parameter, represented by the first n, should be deduced from the initializer n{}, giving it the type n<int>.

This paragraph doesn't specifically address whether the template-argument can come from a default template argument, but even MSVC doesn't think this is actually disallowed: MSVC is happy to accept it when n and f aren't nested within s:

template<typename = int>
struct n {};

template<n = n{}>
constexpr int f();

static_assert(requires { f(); });  // MSVC is fine with this

There is no reason why this should make a difference. MSVC probably just has a bug. It is not clear to me how to work around it.