我试图了解下面的代码段是否应根据标准进行编译。当我尝试使用三个主要编译器的最新版本进行编译时,会发生以下情况:
-std=c++17
标志):编译正常; -std=c++17
标志):也可以正常编译; /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 ]
答案 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;
}
顺便说一句:作为一种快速解决方案,您可以在没有参数包的情况下以自由运行的方式移动通用代码...