我最近将GCC升级到了8.2,并且我的大多数SFINAE表达式都停止了工作。
以下内容略有简化,但演示了该问题:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
GCC的旧版本(不幸的是,我不记得之前安装的确切版本)以及Clang可以很好地编译上面的代码,但是GCC 8.2给出了错误提示:
: In function 'int main()': :29:19: error: call of overloaded 'test()' is ambiguous c.test(); ^ :12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]' void test() { ^~~~ :22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]' void test() { ^~~~ :30:25: error: call of overloaded 'test()' is ambiguous c.test(); ^ :12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]' void test() { ^~~~ :22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]' void test() {
通常,当不同的编译器和编译器版本以不同的方式处理同一代码时,我假定我正在调用未定义的行为。该标准对上述代码有什么要求?我在做什么错了?
注意:问题不是解决此问题的方法,我想到了几种方法。问题是为什么在GCC 8中不起作用-是标准未定义,还是编译器错误?
注释2:由于每个人都在使用默认的void
类型std::enable_if
,因此我将问题改为使用int
。问题仍然存在。
答案 0 :(得分:5)
这是我的看法。简而言之,clang是正确的,而gcc具有回归。
我们根据[temp.deduct]p7:
替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。 [...]
这意味着无论包装是否为空,都必须进行替换。因为我们仍处于即时环境中,所以可以使用SFINAE。
接下来,我们将可变参数确实视为实际的模板参数;来自[temp.variadic]p1
模板参数包是一个接受零个或多个模板参数的模板参数。
和[temp.param]p2表示允许使用哪些非类型模板参数:
非类型模板参数应具有以下类型之一(可选的cv限定):
一种文字类型,具有强等式结构([class.compare.default]),没有可变或易变的子对象,并且如果存在默认成员运算符<=>,则为宣布公开
左值引用类型
包含占位符类型([dcl.spec.auto])的类型,或
推导的类类型([dcl.type.class.deduct])的占位符。
请注意,void
不符合要求,您的代码(发布时)格式不正确。
答案 1 :(得分:1)
我不是语言律师,但以下引用不能以某种方式与问题相关吗?
[temp.deduct.type/9]:如果Pi是一个包扩展,则将Pi的模式与A的模板参数列表中的每个剩余参数进行比较。每个比较都推导模板参数包中后续位置扩展了的模板参数,由皮
在我看来,由于模板参数列表中没有剩余参数,因此没有模式(包含enable_if
)的比较。如果没有比较,那么也就没有扣除,我相信扣除之后会发生替代。因此,如果没有替代项,则不会应用SFINAE。
如果我错了,请纠正我。我不确定该特定段落是否适用于此,但是关于[temp.deduct]中的包扩展,还有更多类似的规则。此外,此讨论还可以帮助更有经验的人解决整个问题:https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A。
答案 2 :(得分:0)
部分答案:将print
与不同的typename = typename enable_if<...>, T=0
一起使用:
T
(demo)
仍然试图弄清楚#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename = typename std::enable_if_t<
std::is_const<typename std::remove_reference<U>::type>::value
>, int = 0
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename = typename std::enable_if_t<
!std::is_const<typename std::remove_reference<U>::type>::value
>, char = 0
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
到底意味着什么,知道default type is void
。