跨编译器在废弃if constexpr(false)语句中实例化模板方面的行为不一致

时间:2019-01-10 10:45:15

标签: c++ language-lawyer c++17

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

  • 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/include\optional(87): error C2182: '_Value': illegal use of type 'void'

C:/msvc/v19_16/include\optional(128): note: see reference to class template instantiation 'std::_Optional_destruct_base<_Ty,false>' being compiled
  with
  [
       _Ty=void
  ]

Live demo

3 个答案:

答案 0 :(得分:19)

绝对是MSVC的错误。 bug report存在,并且据报道已在Visual Studio 2019预览版中修复。


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

  

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

这适用。

  

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

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

  

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

答案 1 :(得分:5)

与@YSC的答案一样,[temp.inst]/10也很相关:

  

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

答案 2 :(得分:1)

我可以观察到该问题仅部分得到解决(VS 16.6.0 Preview 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;
}

顺便说一句:作为一种快速解决方案,您可以在没有参数包的情况下以自由运行的方式移动通用代码...