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

2

26
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
Swirl answered 6/5, 2015 at 13:52 Comment(5)
GCC seems correct, as a static_assert declaration is considered a class member, and should come into being only when the class is instantiated. By the way, you could just leave the primary template a declaration without a definition.Haunch
@Haunch should come into being only when the class is instantiated I don't think that it is true: E.g. static_assert(sizeof(int) != sizeof(int), "some error"); will generate error when member of a template, even if the template is never instantiated.Swirl
@MarcoA. It is not a duplicate because this static_assert is dependent on a template parameterSwirl
Possibly related: gcc.gnu.org/bugzilla/show_bug.cgi?id=53638Kissiah
Note: if all you wish is for Hitchhiker not to be instantiated with random parameters, you can just declare the template as template <int> struct Hitchhiker;. The diagnosis is not customisable, then, but it would work identically on both Clang and gcc.Joachim
B
15

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.

Bigot answered 6/5, 2015 at 15:42 Comment(6)
I disagree with your interpretation of [temp.dep]/1: I take the may as a giving lee-way to the compiler to avoid actually attempting the question of whether the construct will indeed have different semantics in one instantiation or the other, and instead let the compiler decide that if there is a chance it could have different semantics (it mentions the template parameters), then it is considered as depending. I believe this lee-way is extremely important, as the expressions could be arbitrary convoluted and thus determining whether an instantiation is possible or not be complex.Joachim
@MatthieuM. It could be arbitrarily convoluted - that's why it's ill-formed, no diagnostic required. However, I'm not sure the particular passage is relevant here - the "hypothetical instantiation" seems to refer specifically to incomplete types/definitions (see the note).Maritzamariupol
@MatthieuM. The issue in this case is that on one hand we have "the program is ok", and on the other "the program is ill-defined, no diagnostic required". One way to treat "ill formed no diagnostic required" is to compile it into an "ok" program. If the compiler can prove that the sizeof(answer)!=sizeof(answer) is not dependent, it can then evaluate it before instantiation, and find errors and (diagnose) (high QOI) or (engage in arbitrary behavior) (rude QOI). If it doesn't work out that it isn't dependent, nothing in my logic requires the build to fail?Bigot
@Yakk: Oh, I perfectly agree with most of your answer, and the part about compiling or diagnosing being a QOI issue. I had the impression, however, than when you said "So such a construct does not depend on the template parameters" you meant it as the only possible outcome, which does not explain the difference in behavior. Maybe refining this sentence to explain than a compiler may (or not) realize that the construct does not depend on the template parameters would make your point clearer?Joachim
There's a full set of rules to decide whether an expression like sizeof(answer) is type-dependent or value-dependent. Spoiler: It's neither.Fauch
@Yakk-AdamNevraumont "If it doesn't work out that it isn't dependent, nothing in my logic requires the build to fail" -- But doesn't that mean that your logic is wrong, since it (the compiler) then would have to assume that it is dependent, hence not ill-formed, so a compile-time error is required because of the violated static assertion?Neuter
M
16

Both compilers are correct. From [temp.res]/8:

If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required.

There does not exist a valid specialization that can be generated from the primary template Hitchhiker, so it is ill-formed, no diagnostic required. clang chooses to issue a diagnostic anyway.

If you only want to allow 42, then simply don't define the general template:

template <int > struct Hitchhiker;
template <> struct Hitchhiker<42> {};
Maritzamariupol answered 6/5, 2015 at 14:12 Comment(16)
But the template does have a valid specialization.Haunch
there is a valid specialization of the template.Swirl
No there isn't. There is no answer for which sizeof(answer) != sizeof(answer).Maritzamariupol
Your are talking about the primary template which is irrelevant to the explicit specialization Hitchhiker<42>.Haunch
the specialization does not have the static_assert member so it is a valid specializationSwirl
I think Barry is right. No valid specialization can be generated for the primary template, so it is diagnosed at definition time. This is permitted in the standard in some clause I don't have time to find.Mima
Hitchhiker<42> is not a specialization generated for the template Hitchhiker<int >.Maritzamariupol
@0x49 The best tea leaf reading I can make up is "a partial specialization is not a specialization. Each partial specialization is a template, as is the primary template. For a template to have a valid specialization means there is some set of template parameters for which the template is valid. The term 'specialization' here does not refer to 'partial specialization', they are distinct terms. While you think Hitchhiker<42> is a full specialization, it is called a partial specialization in the standard", but it is an argument on very shakey ground.Bigot
Another quote from further down which seems applicable: "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"Spiral
@Tart A problem is, sizeof(answer) != sizeof(answer) is a construct that depends on answer (in a trivial, boring way, but it still depends on it)? Basically false, but false clearly does not depend on answer, while answer != answer does (syntactically).Bigot
@Yakk Yeah, I thought that too and you're most likely right. But I could see a valid argument that the expression evaluates at compile time in the same manner regardless of the parameter, therefore the value is independent of the parameter.Spiral
From [temp.dep] "Inside a template, some constructs have semantics which may differ from one instantiation to another. Such a construct depends on the template parameters". The semantics here do not differ from one instantiation to another, so you could argue that the construct doesn't depend on the parameter.Spiral
@Spiral that is looking like an answer. Write it up!Bigot
The quoted passage however does not require a non-dependent expression. If a compiler can prove that the template cannot be instantiated, it is free to issue a diagnostic even if the cause is a dependent expression or type. Obviously, a diagnostic cannot be required because of Gödel's incompleteness theorems. There are more complex workarounds for "static_assert(false, ...)" in a template that shouldn't be instantiated (by making a dependent expression), but though they appease clang, they are not sufficient for making the program well-formed.Subcommittee
Interesting that the wording changes for [temp.res]/8. Used to be "If no valid specialization can be generated for a template definition, and that template is not instantiated..." The word definition had been removed.Maritzamariupol
@Maritzamariupol That was meant to apply this rule to both template definitions and declarations, not to limit it. +1, BTW.Fauch
B
15

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.

Bigot answered 6/5, 2015 at 15:42 Comment(6)
I disagree with your interpretation of [temp.dep]/1: I take the may as a giving lee-way to the compiler to avoid actually attempting the question of whether the construct will indeed have different semantics in one instantiation or the other, and instead let the compiler decide that if there is a chance it could have different semantics (it mentions the template parameters), then it is considered as depending. I believe this lee-way is extremely important, as the expressions could be arbitrary convoluted and thus determining whether an instantiation is possible or not be complex.Joachim
@MatthieuM. It could be arbitrarily convoluted - that's why it's ill-formed, no diagnostic required. However, I'm not sure the particular passage is relevant here - the "hypothetical instantiation" seems to refer specifically to incomplete types/definitions (see the note).Maritzamariupol
@MatthieuM. The issue in this case is that on one hand we have "the program is ok", and on the other "the program is ill-defined, no diagnostic required". One way to treat "ill formed no diagnostic required" is to compile it into an "ok" program. If the compiler can prove that the sizeof(answer)!=sizeof(answer) is not dependent, it can then evaluate it before instantiation, and find errors and (diagnose) (high QOI) or (engage in arbitrary behavior) (rude QOI). If it doesn't work out that it isn't dependent, nothing in my logic requires the build to fail?Bigot
@Yakk: Oh, I perfectly agree with most of your answer, and the part about compiling or diagnosing being a QOI issue. I had the impression, however, than when you said "So such a construct does not depend on the template parameters" you meant it as the only possible outcome, which does not explain the difference in behavior. Maybe refining this sentence to explain than a compiler may (or not) realize that the construct does not depend on the template parameters would make your point clearer?Joachim
There's a full set of rules to decide whether an expression like sizeof(answer) is type-dependent or value-dependent. Spoiler: It's neither.Fauch
@Yakk-AdamNevraumont "If it doesn't work out that it isn't dependent, nothing in my logic requires the build to fail" -- But doesn't that mean that your logic is wrong, since it (the compiler) then would have to assume that it is dependent, hence not ill-formed, so a compile-time error is required because of the violated static assertion?Neuter

© 2022 - 2024 — McMap. All rights reserved.