简体   繁体   中英

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

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:

  • Clang (version 7.0.0, with -std=c++17 flag): compiles fine;
  • GCC (version 8.2, with -std=c++17 flag): also compiles fine;
  • MSVC (version 19.16, with /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 ]

Live demo

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 form if 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; } { 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 .

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.

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

  • It works in classes without template parameter packs.
  • It works now correctly with in-class definitions but not with out-class definitions.

(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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM