【问题标题】:Inconsistent behaviour across compilers in regard to instantiation of a template in a discarded if constexpr(false) statement关于在丢弃的 if constexpr(false) 语句中实例化模板时,编译器之间的行为不一致
【发布时间】:2019-01-10 10:45:15
【问题描述】:

我试图了解下面的 sn-p 是否应该根据标准编译。当我尝试使用三个主要编译器的最新版本进行编译时,会出现以下情况:

  • 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

【问题讨论】:

  • 看起来像一个 MSVC 错误,因为 IRC 如果 constexpr 下的部分依赖于模板参数,则不应实例化它,除非分支已被采用。
  • 为了它的价值:if constexpr 的动机或多或少正是为了使这样的示例编译。

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


【解决方案1】:

绝对是 MSVC 的错误。 bug report 存在,据报道已在 Visual Studio 2019 Preview 中修复。


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

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

这适用。

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

它也适用,在你的程序{ std::optional&lt;T&gt; val; } 中制作一个丢弃的语句

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

【讨论】:

  • 我下载了 Visual Studio 2019 Preview 并尝试使用随附的编译器版本 19.20 编译代码。您提到的错误报告中的问题确实已解决......但不幸的是我的代码 sn-p 仍然无法编译(我得到与以前相同的编译器错误)。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    我可以观察到该问题仅部分修复(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;
    }
    

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

    【讨论】:

      猜你喜欢
      • 2018-04-10
      • 2020-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 2015-01-16
      • 1970-01-01
      相关资源
      最近更新 更多