Reputation: 937
I am trying to understand whether the snippet below should compile according to The Standard or not. When I try to compile it with latest version of three major compilers, the following occurs:
-std=c++17
flag): compiles fine;-std=c++17
flag): also compiles fine;/std:c++17
flag): compiler error (see below).The error occurs because the MSVC compiler seemingly tries to instantiate std::optional<void>
despite the fact that the code is discarded. GCC and Clang don't seem to do that.
Does The Standard clearly define what should occur in this case?
#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;
}
Error by 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 ]
Upvotes: 22
Views: 1136
Reputation: 2221
I could observe the issue is only partially fixed (VS 16.6.0 Preview 3.0 - cl Version 19.26.28803.1). Now you can observe the following: GodBolt
(the error occurs only in /permissive-
mode which is enabled by default)
#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;
}
BTW: as a quickfix you can move your generic code in free standing function without a parameter pack...
Upvotes: 1
Reputation: 40160
Definitively a bug of MSVC. A bug report exist and has been reportedly fixed in Visual Studio 2019 Preview.
if constexpr
is standardized in [stmt.if]/2
:
If the
if
statement is of the formif constexpr
, the value of the condition shall be a contextually converted constant expression of type bool; this form is called a constexpr if statement.
This applies.
If the value of the converted condition is false, the first substatement is a discarded statement, otherwise [...].
It also applies, making in your program { std::optional<T> val; }
a discarded statement.
During the instantiation of an enclosing templated entity (ndYSC
Bar<void, int>
), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
Upvotes: 20
Reputation: 26810
Along with @YSC's answer, also relevant is [temp.inst]/10
:
An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement , unless such instantiation is required.
Upvotes: 5