编译器之间在丢弃的 if constexpr(false) 语句中实例化模板的行为不一致

Inconsistent behaviour across compilers in regard to instantiation of a template in a discarded if constexpr(false) statement

本文关键字:实例化 语句 不一致 false 之间 if constexpr 编译器      更新时间:2023-10-16

我试图了解下面的代码片段是否应该根据标准进行编译。当我尝试使用三个主要编译器的最新版本编译它时,会发生以下情况:

  • Clang (版本 7.0.0,带 -std=c++17 标志):编译正常;
  • GCC(8.2版,带有-std=c++17标志):也可以编译正常;
  • MSVC(版本 19.16,带/std:c++17标志):编译器错误(见下文)。

发生此错误的原因是 MSVC 编译器似乎尝试实例化std::optional<void>尽管代码已被丢弃。GCC和Clang似乎没有这样做。

《标准》是否明确规定了在这种情况下应该发生什么?

#include <optional>  
#include <type_traits>
template<typename T, typename... Args>
struct Bar
{
  void foo(Args... args)
  {
    if constexpr(!std::is_same_v<T, void>) // false
    {
      // MSVC compiler error occurs because of the line below; no error occurs when compiling with GCC and Clang 
      std::optional<T> val; 
    }
  }
};
int main(int argc, char** argv)
{
  Bar<void, int> inst;
  inst.foo(1);
  return 0;
}

MSVC 的错误:

C:/msvc/v19_16/includeoptional(87): error C2182: '_Value': illegal use of type 'void'
C:/msvc/v19_16/includeoptional(128): note: see reference to class template instantiation 'std::_Optional_destruct_base<_Ty,false>' being compiled
  with
  [
       _Ty=void
  ]

现场演示

绝对是MSVC的一个错误。存在错误报告,据报道已在Visual Studio 2019预览版中修复。


if constexpr[stmt.if]/2中标准化:

如果if语句的形式是if constexpr,则条件的值应是上下文转换的布尔类型的常量表达式;这种形式称为constexpr if语句。

这适用。

如果转换条件的值为 false,则第一个子语句是丢弃的语句,否则 [...]。

它也适用,在您的程序中{ std::optional<T> val; }丢弃语句

在封闭模板化实体(ndYSC Bar<void, int>)的实例化过程中,如果条件在实例化后不依赖于值,则丢弃的子语句(如果有)不会被实例化

除了@YSC的回答外,还有相关的[temp.inst]/10

实现不应隐式实例化函数模板、变量模板、成员模板、非虚拟成员函数、成员类、类模板的静态数据成员或 constexpr if 语句的子语句,除非需要此类实例化。

我可以观察到该问题仅部分修复(VS 16.6.0 预览版 3.0 - cl 版本 19.26.28803.1)。现在您可以观察到以下内容:GodBolt

  • 它适用于没有模板参数包的类。
  • 它现在可以正确处理类内定义,但不能与类外定义一起使用。

(错误仅在默认启用的/permissive-模式下发生)

#include <iostream>
#include <type_traits>
#include <optional>
#define IN_CLASS_DEF_FUNC 0
#define WITH_PARAM_PACK 1
//1, 1 -> works
//0, 1 -> error (invalid use of type void)
//0, 0 -> works
//1, 0 -> works
template<typename T
#if WITH_PARAM_PACK    
    , typename... Args
#endif
>
struct Bar
{
#if IN_CLASS_DEF_FUNC
    void foo()
    {
        if constexpr (!std::is_same_v<T, void>) // false
        {
            // MSVC compiler error occurs because of the line below; no error occurs when compiling with GCC and Clang 
            std::optional<T> val;
        }
    }
#else
    void foo();
#endif
};
#if !IN_CLASS_DEF_FUNC
template<typename T
#if WITH_PARAM_PACK    
    , typename... Args
#endif
>
void Bar<T
#if WITH_PARAM_PACK    
    , Args...
#endif
>::foo()
{
    if constexpr (!std::is_same_v<T, void>) // false
    {
        // MSVC compiler error occurs because of the line below; no error occurs when compiling with GCC and Clang 
        std::optional<T> val;
    }    
}
#endif
int main(int argc, char** argv)
{
    Bar<void> inst;
    inst.foo();
    Bar<int> inst_okay;
    inst_okay.foo();
    return 0;
}

顺便说一句:作为快速修复,您可以在没有参数包的情况下在独立函数中移动通用代码......