Search code examples
c++templateslanguage-lawyerc++14dependent-name

static_assert dependent on non-type template parameter (different behavior on gcc and clang)


template <int answer> struct Hitchhiker {
  static_assert(sizeof(answer) != sizeof(answer), "Invalid answer");
};

template <> struct Hitchhiker<42> {};

While trying to disable general template instantiation with static_assert I discovered that the above code in clang generates the assert error even when the template is not instantiated, while gcc generates the assert error only when instantiating Hitchhiker with a parameter other than 42.

Fiddling around I found that this assert:

template <int answer> struct Hitchhiker {
  static_assert(sizeof(int[answer]) != sizeof(int[answer]), "Invalid answer");
};

template <> struct Hitchhiker<42> {};

behaves the same on both compilers: the assert kicks in only when the general template is instantiated.

What does the standard says, which compiler is right?

g++ 4.9.2
clang++ 3.50

Solution

  • Quotes found by @TartainLlama

    If a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, the program is ill-formed; no diagnostic is required.

    N4296 [temp.res]/8

    This applies immediately after the primary template is defined (the one with the static_assert in it). So the later specialization (for 42) cannot be considered, as it does not exist yet.

    The next question is if static_assert( sizeof(answer) != sizeof(answer), depends on answer. Semantically it does not, syntactically it does, and standard-wise:

    Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a construct depends on the template parameters.

    N4296 [temp.dep]/1

    The construct sizeof(answer) != sizeof(answer) does not differ from one instantiation to another. So such a construct does not depend on the template parameters. Which means the entire static_assert does not depend on the template parameter.

    Thus your program is ill formed, no diagnostic required. Issuing an arbitrary diagnostic (such as the static_assert failing) is valid compiler behavior. Missing the problem is valid compiler behavior. The behavior of a program compiled from an ill formed, no diagnostic required program is not defined by the standard: it is undefined behavior. Nasal demons are permitted.

    Fancy attempts (like sizeof(int[answer])!=sizeof(int[answer]) may please the current god compiler, but does not make your program more well formed.

    You could make a case where the compiler is unlikely to be able to catch you at it, but the ill-formed-ness remains regardless of the ability for the compiler to catch you with it. As a general rule, C++ wants to leave itself (and its compilers) freedom to find invalid template code "earlier than instantiation"; this means that template code must produce possibly legal code.

    It is possible you want something like =delete with a message attached.