C++17:为什么 GCC 根据位置将 SFINAE 表达式计算为两个不同的结果?

Tob*_*lus 5 c++ gcc sfinae c++17

我尝试检测一个类是否已在 C++ 中实现。该类要么仅在之前声明过(情况 1),要么已实现(情况 2)。为了检测类的实现,SFINAE 表达式应该评估sizeof(T)模板参数,如果类不完整,则模板参数将失败。

#include <type_traits>

// 1. Feature not supported: Class only declared
class Test;

// 2. Feature supported: Class implemented
// class Test {};

// Detection if class is complete
template<class T, class Enable = void>
struct is_complete
{
    static constexpr bool value = false;
};

template<class T>
struct is_complete<T, std::enable_if_t<(sizeof(T) == sizeof(T))>>
{
    static constexpr bool value = true;
};

int main(void)
{
    if constexpr (is_complete<Test>::value)
    {
        // static_assert(is_complete<Test>::value, "Test is not complete");
        return 1;
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

问题是 main 方法中 consexpr if 条件的主体是在编译时计算的,即使它在运行时没有使用(程序在执行时返回 0)。如果我static_assert在正文中包含 a ,则会产生以下错误消息:

<source>:20:9: error: static_assert failed due to requirement 'is_complete<Test, void>::value' "Test is not complete"
        static_assert(is_complete<Test>::value, "Test is not complete");
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

因此,这is_complete<Test>::value似乎在 if 条件体内评估为 false,而 if 条件内的相同表达式在编译期间评估为 true,但在运行时则不会。我可以在 GCC 11.2 和 CLANG 12.0.1 中重现此行为。

我也尝试更改sizeof(T) == sizeof(T)sizeof(T) > 0相同的结果。

如果我尝试实例化该类,然后将其转换为它的基类,我可以观察到相同的行为,如以下代码所示:

<source>:20:9: error: static_assert failed due to requirement 'is_complete<Test, void>::value' "Test is not complete"
        static_assert(is_complete<Test>::value, "Test is not complete");
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

然后编译器会抱怨 的强制转换unique_ptr,如果该类已实现,则该强制转换有效:

<source>: In function 'std::unique_ptr<Base> Build()':
<source>:33:38: error: could not convert 'make_unique<Test>()' from 'unique_ptr<Test,default_delete<Test>>' to 'unique_ptr<Base,default_delete<Base>>'
   33 |         return std::make_unique<Test>();
      |                ~~~~~~~~~~~~~~~~~~~~~~^~
      |                                      |
      |                                      unique_ptr<Test,default_delete<Test>>
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

所以我现在的问题是:

  1. 我到底做错了什么?
  2. 这是该语言的错误还是“功能”?
  3. 有解决此限制的方法吗?