Fab*_* A. 9 c++ g++ language-lawyer clang++ c++20
在 c++20 中玩弄文字、非类型模板参数,我发现 g++ 和 clang++ 不同意以下代码。
#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);
Run Code Online (Sandbox Code Playgroud)
在 Godbolt 上实时查看:https ://godbolt.org/z/f3afjd
首先也是最重要的,我什至不确定我是否以正确的方式做这件事:它是与泛型StringLiteral<N>类型匹配的方式,还是不是?如果不是,正确的方法是什么?
而且,为什么编译器不同意呢?谁有错误?
编辑:发现删除size_t N部分特化中的参数使两个编译器都同意,结果是预期的。像这样:
template <typename T, StringLiteral Name>
struct is_named<named<T, Name>>: std::true_type{};
Run Code Online (Sandbox Code Playgroud)
但是,我仍然很好奇我的第一次尝试是否符合标准,以及哪个编译器出错了。
让我们专注于 的部分专业化is_named:
Run Code Online (Sandbox Code Playgroud)template <typename T, size_t N, StringLiteral<N> Name> struct is_named<named<T, Name>>: std::true_type{};
并特别尝试回答它是否违反[temp.class.spec.match]/3:
如果由于模板参数列表和模板 id 的结构无法推导出部分特化的模板参数,则程序格式错误。
注意到 Clang 显然不这么认为,并使用主模板的单个模板参数来推导部分特化的所有模板参数。在这种特殊情况下,这些模板参数是那些与其模板参数列表匹配的参数:
TNName[...] 如果模板参数仅在非推导上下文中使用且未明确指定,则模板参数推导失败。[...]
我们可以打破的问题分解成是否所有三个模板参数T,N和Name部分特的使用在至少一个推导的上下文或没有。
可以在几种不同的上下文中推导出模板参数,但在每种情况下,根据模板参数指定的类型(调用它
P)与实际类型(调用它A)进行比较,并尝试找到模板参数值(类型参数的类型、非类型参数的值或模板参数的模板)P,在替换推导值(称为推导A)后,将使与 兼容A。
和[temp.deduct.type]/3和(再次)/4:
/3 给定的类型 P 可以由许多其他类型、模板和非类型值组成:
- [...]
- 作为类模板(例如,
A<int>)的特化的类型包括特化的模板参数列表所引用的类型、模板和非类型值。[...]/4 在大多数情况下,用于组合的类型、模板和非类型值
P参与模板参数推导。
我们可以不失一般性,考虑实际类型的模板参数为主要模板的单一类型的模板参数(这是我们打算要FO家庭的类型,其偏特适用于本部分),也就是说A,作为named<int, StringLiteral<5>{"ciao"}>。
根据偏特化的模板参数指定的类型,例如P,是named<T, Name>。
T可以简单地推断,匹配A/ Pas named<int, StringLiteral<5>{"ciao"}>/ named<T, Name>, to int,因为Tinnamed<T, Name>不在非推断上下文中。Name类似地不在非推导的上下文中,并且可以推导为StringLiteral<5>{"ciao"}。N,它不是 的显式部分P,而是通过模板参数隐式地如此Name。然而,在这里我们可以简单地递归地应用演绎规则:Name已被演绎为StringLiteral<5>{"ciao"},这意味着我们考虑一个新的A/P对StringLiteral<5>/ StringLiteral<N>,其中 N 在不可N演绎的上下文中是非,因此最终可以被演绎为5。而且,为什么编译器不同意呢?谁有错误?
因此, Clang (以及MSVC)接受您的原始变体是正确的,而 GCC 拒绝它是错误的(拒绝有效错误)。
Clang和MSVC(正确)接受并(错误地)被GCC拒绝的一个更简单的例子是:
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
Run Code Online (Sandbox Code Playgroud)
我已经使用这个例子提交了一个错误报告:
[...] 删除
size_t N部分特化中的参数使两个编译器都同意,[...]
在您的第二个变体中,推导情况不像第一个变体那么复杂,您可以使用类似的分析来查看它是否同样格式良好(部分特化的所有模板参数都是可推导的)。