为什么我的 SFINAE 表达式不再适用于 GCC 8.2?

Why do my SFINAE expressions no longer work with GCC 8.2?

本文关键字:GCC 适用于 不再 我的 SFINAE 表达式 为什么      更新时间:2023-10-16

我最近将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( – 在线试用

C++ (叮当声( – 在线试用

旧版本的GCC(不幸的是我不记得我以前安装的确切版本(以及Clang编译上述代码就可以了,但是GCC 8.2给出了一个错误,指出:

:在函数 'int main((' 中: :29:19:错误:重载的"test(("调用不明确  c.test((;  ^ :12:10: 注意: 候选: 'void Class::test(( [与 U = int&; typename std::enable_if::type>::value>::type ... = {}]'  void test(( { ^~~~ :22:10: 注意: 候选: 'void Class::test(( [与 U = int&; typename std::enable_if::type>::value(>::type ... = {}]'  void test(( { ^~~~ :30:25:错误:重载的"test(("调用不明确  c.test((;  ^ :12:10: 注意: 候选: 'void Class::test(( [与 U = const int&; typename std::enable_if::type>::value>::type ... = {}]'  void test(( { ^~~~ :22:10: 注意: 候选: 'void Class::test(( [与 U = const int&; typename std::enable_if::type>::value(>::type ... = {}]'  void test(( {

当不同的编译器和编译器版本以不同的方式处理相同的代码时,通常会出现这种情况,我假设我正在调用未定义的行为。标准对上述代码有什么看法?我做错了什么?


注意:问题不在于解决此问题的方法,而是想到了几个问题。问题是为什么这不适用于 GCC 8 - 它是标准未定义的,还是编译器错误?

注意2:由于每个人都跳上默认void类型的std::enable_if,我已将问题更改为使用int。问题依然存在。

注3:已创建GCC错误报告

这是我的看法。简而言之,clang是对的,gcc有一个回归。

根据 [温度扣除]p7,我们有:

替换发生在函数类型和模板参数声明中使用的所有类型和表达式中。[...]

这意味着无论包装是否为空,都必须进行替换。因为我们仍然处于直接的背景下,所以这是可以的。

接下来,我们得到一个可变参数确实被认为是一个实际的模板参数;来自 [temp.variadic]p1

模板参数

包是接受零个或多个模板参数的模板参数

和 [temp.param]p2 表示允许哪些非类型模板参数:

非类型模板参数应具有以下类型之一(可选符合 cv 条件(:

文字
  • 类型,具有很强的结构相等性([class.compare.default](,没有可变或易失性的子对象,并且如果存在默认的成员运算符 <=>,则声明为 public,

  • 左值引用类型,

  • 包含占位符类型 ([dcl.spec.auto]( 的类型,或

  • 推导类类型的占位符 ([dcl.type.class.deduct](。

请注意,void不符合要求,则您的代码(已发布(格式不正确。

我不是语言律师,但以下引用是否与问题有某种联系?

[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。

部分答案:使用具有不同Ttypename = typename enable_if<...>, T=0

#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;
}

(演示(

仍然试图弄清楚知道默认类型是voidstd::enable_if<...>::type...到底是什么意思。