Clang vs. GCC:未评估上下文中的错误会中断 SFINAE

Clang vs. GCC: Error in unevaluated context breaks SFINAE

本文关键字:错误 误会 中断 SFINAE 上下文 GCC vs 评估 Clang      更新时间:2023-10-16

在继续我上一个问题的工作时,我遇到了 clang 和 GCC 的不同行为。
我需要检查成员函数指针,因为我需要知道该函数是否被继承。

在SFINAE上下文中比较成员函数指针时,成员函数Foo::foo()存在,但其主体包含最终无法编译的代码(x.hello())。

以下代码使用 clang 进行编译。然而,GCC 似乎评估了 Foo::foo() 的函数体并以错误('struct Caller' has no member named 'hello')退出,尽管处于未评估的 SFINAE 上下文中(或者我希望如此)。

#include <iostream>
#include <type_traits>
struct Foo
{
    template <typename T> void foo(T&& x) { x.hello(); }
};
struct Caller
{    
    template <typename T>
    auto call(T&& x) -> decltype(
        std::enable_if_t<
            std::is_same<
                decltype(&T::template foo<decltype(*this)>),
                void (T::*)(decltype(*this))
            >::value
        >())
    {
        //x.foo(*this);
    }
};
int main()
{
  Caller c;
  c.call(Foo());
}

我测试了:

  • 叮当 3.8.0
  • g++ 6.1.0

两者的编译器选项:-std=c++14 -O2 -Wall -pedantic -pthread

现场示例

我的问题:

  1. 谁是对的?叮当还是海湾合作委员会?
  2. 如何获取代码以使用 GCC 进行编译?

回答第二个问题

如何获取代码以使用 GCC 进行编译?

我会简化传递给decltype()的表达式。如果call方法的编译依赖于x.foo(*this);的编译,那么这就是你应该使用的。

struct Foo
{
    template <typename T> void foo(T&& x) { x.hello(); }
};
struct Caller
{    
    template <typename T>
    auto call(T&& x, int) -> decltype(x.foo(*this))
    {
        //x.foo(*this);
    }
    template <typename T>
    void call(T&&, char){ std::cout << "hello" << std::endl;}
};
int main()
{
  Caller c;
  c.call(Foo(), 0);
}

在这里演示。


我认为 op 对 gcc 的问题在于函数的地址(如果没有明确采用,则会衰减到函数指针)。我认为这是标准中的一个极端情况。如果需要方法Foo::foo,则x.hello()需要存在(编译);一般来说,获取某物的地址可以满足这一点,但在未评估的上下文中(decltype()),我不确定这是否适用 - 当然 clang 不需要它存在(MSVC 也不要求)。

在这方面,

谁是对的?叮当还是海湾合作委员会?

我怀疑 clang 实现了对标准的更宽松的解读,并且可能更正确的解读。decltype()的操作数是未计算的操作数,请参阅 [dcl.type.simple]/4;

decltype 说明符的操作数是未计算的操作数(子句 [expr])。