Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is static_assert supposed to work when invoked via decltype expression?

I expect the following code to fail with a static_assert check on the final line. However in MSVC2015 and gcc 6.2, it compiles sucessfully. It does fail to compile as expected in clang 3.9. Is this a compiler bug or does static_assert not work inside decltype()?

#include <tuple>
#include <type_traits>

template<typename T>
struct Wrapper {};

template<typename T, typename U>
constexpr std::tuple<T, U> operator|(Wrapper<T>, Wrapper<U>)
{
    static_assert(std::is_same<T,U>::value == false, "can't combine two of the same type");
    return std::tuple<T, U> {};
}

struct A {};
struct B {};
constexpr Wrapper<A> aaa = {};
constexpr Wrapper<B> bbb = {};

constexpr auto shouldPass1 = aaa | bbb;
//constexpr auto shouldFail1 = aaa | aaa; // fails static assert as expected
using shouldFail2 = decltype(aaa | aaa);
// ^ doesn't fail in MSVC2015, or gcc 6.2. does fail in clang 3.9

Update #1: Additional Question

Brian suggested that the static_assert would not fire in the decltype context because the value has not been explicitly instantiated. So I added an additional test below to explicitly instantiate the shouldFail2 type , which I think by Brian's logic should cause the static_assert to fail. However, the code below does not fail in MSVC2015 or gcc 6.2. Is this one a bug, or have I overlooked something? Edit: It appears that once decltype has extracted the type, we are free to use shouldFail2 without further reference to the definition of operator|.

shouldFail2 shouldFail3 = {}; // instantiate shouldFail2.
// ^ doesn't fail in MSVC2015 or gcc 6.2.

Update #2

If I change the definition of operator| to use an auto (or decltype(auto)) with no trailing return type, then the decltype expression correctly fails the static_assert in gcc 6.2. However this version fails to compile in MSVC2015 (errors C3779, C2088). Edit: as W.F. points out below, omitting the trailing return type is a C++14 feature.

template<typename T, typename U>
constexpr auto operator|(Wrapper<T>, Wrapper<U>)
{
    static_assert(std::is_same<T,U>::value == false, "can't combine two of the same type");
    return std::tuple<T, U> {};
}

...

using shouldFail2 = decltype(aaa | aaa);
// ^ now this correctly fails the static_assert in gcc 6.2
like image 629
Ross Bencina Avatar asked Dec 07 '16 06:12

Ross Bencina


1 Answers

I believe GCC and MSVC are correct, and Clang is incorrect. The static_assert should not fire, because according to the Standard at [temp.inst]/3:

Unless a function template specialization has been explicitly instantiated or explicitly specialized, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist.

Inside an unevaluated context such as decltype, it is valid to have a call to a function that is left undefined, so this is not such a context in which the function definition is required to exist. Therefore the static_assert declaration in the body of the specialization is not instantiated.

like image 160
Brian Bi Avatar answered Oct 26 '22 10:10

Brian Bi