为啥我的 SFINAE 表达式不再适用于 GCC 8.2?
Posted
技术标签:
【中文标题】为啥我的 SFINAE 表达式不再适用于 GCC 8.2?【英文标题】:Why do my SFINAE expressions no longer work with GCC 8.2?为什么我的 SFINAE 表达式不再适用于 GCC 8.2? 【发布时间】:2018-08-10 13:31:27 【问题描述】:我最近将 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;
C++ (gcc) – Try It Online
C++ (clang) – Try It Online
旧版本的 GCC(不幸的是我不记得我之前安装的确切版本)以及 Clang 编译上面的代码就好了,但是 GCC 8.2 给出了一个错误说明:
:在函数'int main()'中: :29:19: 错误: 重载 'test()' 的调用不明确 c.test(); ^ :12:10: 注意:候选人:'void Class::test() [with U = int&;类型名 std::enable_if::type>::value>::type ... = ]' 无效测试() ^~~~ :22:10: 注意:候选人:'void Class::test() [with U = int&;类型名 std::enable_if::type>::value)>::type ... = ]' 无效测试() ^~~~ :30:25: 错误: 重载 'test()' 的调用不明确 c.test(); ^ :12:10: 注意:候选人:'void Class::test() [with U = const int&;类型名 std::enable_if::type>::value>::type ... = ]' 无效测试() ^~~~ :22:10: 注意:候选人:'void Class::test() [with U = const int&;类型名 std::enable_if::type>::value)>::type ... = ]' 无效测试()
通常情况下,不同的编译器和编译器版本以不同的方式处理相同的代码,我假设我正在调用未定义的行为。标准对上述代码有什么说法?我做错了什么?
注意:问题不在于解决此问题的方法,我想到了几种方法。问题是为什么这不适用于 GCC 8 - 它是标准未定义的,还是编译器错误?
注意 2: 由于每个人都在使用默认的 void
类型的 std::enable_if
,因此我将问题更改为使用 int
。问题依然存在。
注3:GCC bug report created
【问题讨论】:
从 godbolt 开始,它一直工作到gcc 7.3
(你可以在程序集中看到它做了正确的事情)。
在::type
之后用...
扩展什么?
删除省略号,将默认的void
替换为int
并添加默认值可以解决问题,是的。真正的问题是,上述代码以前运行良好,但在 GCC 8 中不再运行的原因是什么?
@xskxzr 根据这个问题,void...
仅适用于空参数包,这正是这里所需要的。即使它是非法的,在std::enable_if
中用int
替换默认的void
也不会改变问题的任何内容。
最小示例:godbolt.org/g/P9z1pt gcc7.1 OK gcc 8.x KO
【参考方案1】:
这是我的看法。总之,clang是对的,gcc有回归。
我们有照[temp.deduct]p7:
替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。 [...]
这意味着无论包是否为空,都必须进行替换。因为我们仍处于直接上下文中,所以这是 SFINAE-able。
接下来我们知道可变参数确实被认为是一个实际的模板参数;来自[temp.variadic]p1
模板参数包是接受零个或多个模板参数的模板参数。
而[temp.param]p2 表示允许哪些非类型模板参数:
非类型模板参数应具有以下类型之一(可选 cv 限定):
一种文字类型,具有强结构相等性([class.compare.default]),没有可变或易失的子对象,并且如果存在默认成员运算符,则将其声明为公共的,
左值引用类型,
包含占位符类型 ([dcl.spec.auto]) 的类型,或
推导类类型的占位符 ([dcl.type.class.deduct])。
请注意,void
不符合要求,您的代码(如发布的)格式不正确。
【讨论】:
问题不在于std::enable_if
的void
默认类型!如果我正确理解您的答案,SFINAE 应该适用于不同的类型(例如int
)。它不在 GCC 8.2 下。
@zennehoy 这就是为什么我在第二句话中说“如果不是void
,则回归”:)
你介意减少你的答案吗?我现在在问题中将int
指定为std::enable_if
的类型,因为我真的不打算开始讨论void...
,这原本是我简化代码的遗物。请注意,其他人得出的结论与您不同:***.com/a/23711944/694509 :)
@zennehoy 我不太明白答案;不过谢谢
@zenn 其他答案不正确,因为这不是包扩展。它只是一个非扩展模板参数包,如果你给它一个名字,它可以在其他地方扩展。【参考方案2】:
我不是语言律师,但下面的引用不能与问题有某种联系吗?
[temp.deduct.type/9]: 如果 Pi 是包扩展,则将 Pi 的模式与 A 的模板参数列表中的每个剩余参数进行比较。每次比较都会推导出由 Pi 扩展的模板参数包中后续位置的模板参数。
在我看来,由于模板参数列表中没有剩余参数,因此没有比较模式(其中包含enable_if
)。如果没有比较,那么也没有扣除,我相信扣除后会发生替代。因此,如果没有替换,则不会应用 SFINAE。
如果我错了,请纠正我。我不确定这个特定的段落是否适用于此,但在 [temp.deduct] 中有更多类似的关于包扩展的规则。此外,此讨论可以帮助更有经验的人解决整个问题:https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A。
【讨论】:
有趣的是,讨论得出的结论是,这是 Clang 中的一个错误,随后被修复!标准中似乎有相当多的不确定性......【参考方案3】:部分答案:将typename = typename enable_if<...>, T=0
与不同的T
s 一起使用:
#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;
(demo)
仍在试图弄清楚 std::enable_if<...>::type...
到底是什么意思知道 default type is void
。
【讨论】:
见***.com/a/23711944/694509,基本要求参数包为空。随意添加int
来替换默认的 void
类型 - 这不会改变问题的任何内容。
@zennehoy 注意:如果没有添加 tparam (int
& char
),您的两个模板函数具有相同的模板签名,因此会产生歧义。我不明白为什么它以前会起作用。
怎么样?其中一个是template <typename U, void...>
,另一个是SFINAE,即template <typename U, [error: std::enable_if has no member type]...>
。
@YSC: typename = std::enable_if_t<cond>
需要“奇怪”的额外虚拟参数,最好使用std::enable_if_t<cond, int> = 0
。以上是关于为啥我的 SFINAE 表达式不再适用于 GCC 8.2?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Office 365 连接到 office 按钮不再适用于新的 Microsoft Teams 组?