Toying around with literal, non-type template parameters in c++20, I found out that g++ and clang++ disagree about the following code.
#include <algorithm>
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template <typename T, StringLiteral Name>
struct named{};
template <typename T>
struct is_named: std::false_type{};
template <typename T, size_t N, StringLiteral<N> Name>
struct is_named<named<T, Name>>: std::true_type{};
// This will fail with g++
static_assert(is_named<named<int, "ciao">>::value == true);
See it live on godbolt: https://godbolt.org/z/f3afjd
First and foremost, I'm not even sure I'm doing it the right way: is it that the way one matches with a generic StringLiteral<N>
type, or is it not? If not, what's the right way?
And, why do the compilers disagree about it? Who's got the bug?
EDIT: found out that removing the size_t N
parameter in the partial specialization makes both compilers agree, and the result is the expected one. Like this:
template <typename T, StringLiteral Name>
struct is_named<named<T, Name>>: std::true_type{};
However, I'm still curious about whether my 1st attempt is legit, by the standard, and which compiler got it wrong.
Let's focus on the partial specialization of is_named
:
template <typename T, size_t N, StringLiteral<N> Name> struct is_named<named<T, Name>>: std::true_type{};
and particularly try to answer whether it violates [temp.class.spec.match]/3 or not:
If the template arguments of a partial specialization cannot be deduced because of the structure of its template-parameter-list and the template-id, the program is ill-formed.
noting that Clang apparently does not think so, and uses the single template argument to the primary template to deduce all template arguments of the partial specialization. In this particular case these template arguments are those matching its template-parameter-list:
T
N
Name
As per [temp.deduct.type]/4:
[...] If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails. [...]
we can break the question down into whether all three template parameters T
, N
and Name
of the partial specialization are used in least one deduced context or not.
Starting from [temp.deduct.type]/1
Template arguments can be deduced in several different contexts, but in each case a type that is specified in terms of template parameters (call it
P
) is compared with an actual type (call itA
), and an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will makeP
, after substitution of the deduced values (call it the deducedA
), compatible withA
.
and [temp.deduct.type]/3 and (again) /4:
/3 A given type P can be composed from a number of other types, templates, and non-type values:
- [...]
- A type that is a specialization of a class template (e.g.,
A<int>
) includes the types, templates, and non-type values referenced by the template argument list of the specialization. [...]/4 In most cases, the types, templates, and non-type values that are used to compose
P
participate in template argument deduction.
We can without loss of generality consider the actual type of the template argument for the single type template parameter of the primary template (which we intend to be part fo the family of types for which partial specialization to applies), say A
, as named<int, StringLiteral<5>{"ciao"}>
.
The type specified in terms of the template parameters of the partial specialization, say P
, is named<T, Name>
.
T
can be trivially deduced, matching A
/P
as named<int, StringLiteral<5>{"ciao"}>
/named<T, Name>
, to int
, as T
in named<T, Name>
is not in a non-deduced context.Name
is similarly not in a non-deduced context, and can be deduced to StringLiteral<5>{"ciao"}
.N
, which is not explicitly part of P
, but only implicitly so via the template parameter Name
. However, here we may simply apply the deduction rules recursively: Name
has been deduced to StringLiteral<5>{"ciao"}
, meaning we consider a new A
/P
pair StringLiteral<5>
/StringLiteral<N>
, in which N is non in a non-deducible context, and N
can thus ultimately be deduced to 5
.And, why do the compilers disagree about it? Who's got the bug?
Consequently, Clang (as well as MSVC) are correct to accept your original variant, whereas GCC is wrong to reject it (rejects-valid bug).
A more minimal example which is (correctly) accepted by Clang and MSVC, and (incorrectly) rejected by GCC is:
template<int N> struct S {};
template<S s> struct U {};
template<typename> struct V { V() = delete; };
template <int N, S<N> s>
struct V<U<s>> {};
V<U<S<0>{}>> v{};
// Expected: use partial specialization #1
// GCC actual: error (rejects-valid): use of deleted function
and I have filed a bug report using this example:
[...] removing the
size_t N
parameter in the partial specialization makes both compilers agree, [...]
In your second variant, the deduction case is not as complex as the first one, and you can use a similar analysis to see that is likewise well-formed (all template parameters of the partial specialization are deducible).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With