I read a couple of answers yesterday, on the use of static_assert(false, "Some message") inside the else clause of an if constexpr. I understand that it's considered to be ill-formed, according to the standard (even if some compilers, including MSVC2017, will accept it). Qt will also mark this as an error.
My question is, is the code below well-formed according to the standard? (I'm inclined to think so, but I'd like a confirmation.)
template <typename TypeOfValue>
static void PushValue(duk_context* ctx, TypeOfValue value) {
// Push value onto duktape stack
if constexpr (std::is_same<TypeOfValue, int>::value) {
// Push int
duk_push_int(ctx, value);
} else if constexpr (std::is_same<TypeOfValue, uint32_t>::value) {
// Push uint
duk_push_uint(ctx, value);
} else {
// Unsupported type
static_assert(bool_value<false, TypeOfValue>(), "Unsupported type");
}
}
template <bool value, typename T>
static constexpr bool bool_value() {return value;}
Edit:
It seems, from the comment I got, that bool_value should instead be defined like this:
template<bool value, typename T>
struct bool_value {
static constexpr bool value = value;
};
with the usage pattern
// Unsupported type
static_assert(bool_value<false, TypeOfValue>::value, "Unsupported type");
It's then well-formed, only because it's possible for bool_value to be specialized into a version which returns true for the expression bool_value<false, TypeOfValue>::value.
Both of your attempts (with the function and with the struct) are well-formed as is.
The other answer mentions [temp.res]/8, but I disagree with how it was interpreted.
The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or ...
Both the function and struct you wrote can be specialized to be true. I believe the mere possibility of specialization is enough, you don't actually need to add a dummy true specialization to make the code well-formed.
According to my understanding (and according to common sense, I hope), the point of this part of the standard is to allow compilers to check validity of templates and if constexpr branches early (when they are seen for the first time), and reject the ones that can't possibly be instantiated.
Every branch in your template can potentially be instantiated, because bool_value() can be specialized later. I doubt a sane compiler is going to reject your code due to bool_value() not being specialized yet.
The accepted answer of the question you linked explains why it's ill-formed. Note the quote from the Standard temp.res-8, which says (emphasis mine)
The validity of a template may be checked prior to any instantiation. [...] The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or...
[...]
Otherwise, no diagnostic shall be issued for a template for which a valid specialization can be generated.
[ Note: If a template is instantiated, errors will be diagnosed according to the other rules in this document.
Exactly when these errors are diagnosed is a quality of implementation issue.
— end note
]
In the cppreference page about if you can see the following workaround (emphasis mine)
The common workaround for such a catch-all statement is a type-dependent expression that is always false:
template<class T> struct dependent_false : std::false_type {};
template <typename T>
void f() {
if constexpr (std::is_arithmetic_v<T>)
// ...
else
static_assert(dependent_false<T>::value, "Must be arithmetic"); // ok
}
As noted in the comments by Evg
theoretically, this is still ill-formed, because [...] is always false unless you actually have a specialization that returns true.
So you could add that specialization to the previous snippet
namespace {
// Do not unleash.
struct aggressive_unicorn_type;
}
template<> struct dependent_false<aggressive_unicorn_type> : std::true_type {};
Whether this is necessary to satisfy the Standard document to the letter or not it depends on the interpretation of "can be generated".
I wonder if it really matters too. The OP is asking if the code is well-formed, but they are actually trying to produce a code which is ill-formed, with a diagnostic (that's what static_assert(false) implies, see dcl.pre) under certain conditions.
As the asker already noted, there are other methods to force a compiler error when arguments of the "wrong" type are passed (consider concepts, of the upcoming standard) and, maybe, if we want to use static_asserts in conjunction with if constexpr the best option may be a type-dependent expression that is not always false.
#include <type_traits>
void f(int) {};
void g(unsigned) {};
template< class T>
constexpr inline bool is_supported =
std::is_same_v<T, int> ||
std::is_same_v<T, unsigned> ||
std::is_same_v<T, char>;
template <class T>
void use(T value) {
static_assert(is_supported<T>, "Not supported");
if constexpr (std::is_same<T, int>::value) {
f(value);
} else if constexpr (std::is_same_v<T, unsigned>){
g(value);
} else {
static_assert( !is_supported<T>, "Not yet implemented");
}
}
I'll just add my own final thoughts as an answer, since I seem to not be getting a quite principled enough answer from other sources.
My conclusion after reading a couple of other answers is this:
The compiler is allowed, according to the standard, to evaluate static_assert(false, "...") early. If it does, it produces a program that can never compile.
A program that can never compile is ill-formed.
So, the problem is that the compiler is allowed to evaluate static_assert early, even within an if constexpr.
Forcing it to delay the evaluation until instantiation time, by introducing a false that artificially depends upon a template parameter, seems a bit contrived to me. But I accept that there are other considerations which probably makes it the correct language choice anyway. (That's the sort of thing I was hoping for an expert answer to shed some light upon.)
Related
P0292R1 constexpr if has been included, on track for C++17. It seems useful (and can replace use of SFINAE), but a comment regarding static_assert being ill-formed, no diagnostic required in the false branch scares me:
Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.
void f() {
if constexpr (false)
static_assert(false); // ill-formed
}
template<class T>
void g() {
if constexpr (false)
static_assert(false); // ill-formed; no
// diagnostic required for template definition
}
I take it that it's completely forbidden to use static_assert inside constexpr if (at least the false / non-taken branch, but that in practice means it's not a safe or useful thing to do).
How does this come about from the standard text? I find no mentioning of static_assert in the proposal wording, and C++14 constexpr functions do allow static_assert (details at cppreference: constexpr).
Is it hiding in this new sentence (after 6.4.1) ? :
When a constexpr if statement appears in a templated entity,
during an instantiation of the enclosing template or generic lambda,
a discarded statement is not instantiated.
From there on, I assume that it is also forbidden, no diagnostic required, to call other constexpr (template) functions which somewhere down the call graph may call static_assert.
Bottom line:
If my understanding is correct, doesn't that put a quite hard limit on the safety and usefulness of constexpr if as we would have to know (from documentation or code inspection) about any use of static_assert? Are my worries misplaced?
Update:
This code compiles without warning (clang head 3.9.0) but is to my understanding ill-formed, no diagnostic required. Valid or not?
template< typename T>
constexpr void other_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template<class T>
void g() {
if constexpr (false)
other_library_foo<T>();
}
int main(){
g<float>();
g<int>();
}
This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }. [temp.res]/8 with the new change bolded:
The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a substatement
of a constexpr if statement ([stmt.if]) within a
template and the template is not instantiated, or
[...]
No valid specialization can be generated for a template containing static_assert whose condition is nondependent and evaluates to false, so the program is ill-formed NDR.
static_asserts with a dependent condition that can evaluate to true for at least one type are not affected.
C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with a bool template non-type parameter that we use to trigger the static_assert. We immediately invoke the lambda with (), but since the lambda won't be instantiated if its else branch is not taken, the assertion will not trigger unless that else is actually taken:
template<typename T>
void g()
{
if constexpr (case_1)
// ...
else if constexpr (case_2)
// ...
else
[]<bool flag = false>()
{static_assert(flag, "no match");}();
}
Edit: I'm keeping this self-answer with examples and more detailed explanations of the misunderstandings that lead to this questions. The short answer by T.C. is strictly enough.
After rereading the proposal and on static_assert in the current draft, and I conclude that my worries were misguided. First of all, the emphasis here should be on template definition.
ill-formed; no diagnostic required for template definition
If a template is instantiated, any static_assert fire as expected. This presumably plays well with the statement I quoted:
... a discarded statement is not instantiated.
This is a bit vague to me, but I conclude that it means that templates occurring in the discarded statement will not be instantiated. Other code
however must be syntactically valid. A static_assert(F), [where F is false, either literally or a constexpr value] inside a discarded if constexpr clause will thus still 'bite' when the template containing the static_assert is instantiated. Or (not required, at the mercy of the compiler) already at declaration if it's known to always be false.
Examples: (live demo)
#include <type_traits>
template< typename T>
constexpr void some_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template< typename T>
constexpr void other_library_bar(){
static_assert(std::is_same<T,float>::value);
}
template< typename T>
constexpr void buzz(){
// This template is ill-formed, (invalid) no diagnostic required,
// since there are no T which could make it valid. (As also mentioned
// in the answer by T.C.).
// That also means that neither of these are required to fire, but
// clang does (and very likely all compilers for similar cases), at
// least when buzz is instantiated.
static_assert(! std::is_same<T,T>::value);
static_assert(false); // does fire already at declaration
// with latest version of clang
}
template<class T, bool IntCase>
void g() {
if constexpr (IntCase){
some_library_foo<T>();
// Both two static asserts will fire even though within if constexpr:
static_assert(!IntCase) ; // ill-formed diagnostic required if
// IntCase is true
static_assert(IntCase) ; // ill-formed diagnostic required if
// IntCase is false
// However, don't do this:
static_assert(false) ; // ill-formed, no diagnostic required,
// for the same reasons as with buzz().
} else {
other_library_bar<T>();
}
}
int main(){
g<int,true>();
g<float,false>();
//g<int,false>(); // ill-formed, diagnostic required
//g<float,true>(); // ill-formed, diagnostic required
}
The standard text on static_assert is remarkably short. In standardese, it's a way to make the program ill-formed with diagnostic (as #immibis also pointed out):
7.6 ... If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and
the resulting diagnostic message (1.4) shall include the text of the
string-literal, if one is supplied ...
The most concise way I've come across to work-around this (at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:
template<class T>
void g() {
if constexpr (can_use_it_v<T>) {
// do stuff
} else {
// can't use 'false' -- expression has to depend on a template parameter
static_assert(!sizeof(T*), "T is not supported");
}
}
The point of using T* is to still give the proper error for incomplete types.
I also came across this discussion in the old isocpp mailing list which may add to this discussion. Someone there brings up the interesting point that doing this kind of conditional static_assert is not always the best idea, since it cannot be used to SFINAE-away overloads, which is sometimes relevant.
Your self-answer and possibly the one by T.C. are not quite correct.
First of all, the sentence "Both two static asserts will fire even though within if constexpr" is not correct. They won't because the if constexpr condition depends on a template parameter.
You can see that if you comment out the static_assert(false) statements and the definition of buzz() in your example code: static_assert(!IntCase) won't fire and it will compile.
Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T> are allowed (and have no effect) inside a discarded constexpr if, even if there's no T for which they evaluate to true.
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). It should say "could be generated", with further clarification of what is meant by "could".
This is related to the question whether AlwaysFalse<T>::value and ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about).
I would argue that they are, since it's "can" and not "could" and both are false for all types at the point of their instantiation.
The crucial difference between std::is_same and the non-standard wrapper here is that the latter could theoretically be specialized (thanks, cigien, for pointing this out and providing the link).
The question whether ill-formed NDR or not also crucially depends on whether the template is instantiated or not, just to make that entirely clear.
My solution is:
if constexpr (is_same_v<T,int>)
// ...
else if constexpr (is_same_v<T,float>)
// ...
else
static_assert(std::is_same_v<T, void> && !std::is_same_v<T, void>, "Unsupported element type.");
P0292R1 constexpr if has been included, on track for C++17. It seems useful (and can replace use of SFINAE), but a comment regarding static_assert being ill-formed, no diagnostic required in the false branch scares me:
Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.
void f() {
if constexpr (false)
static_assert(false); // ill-formed
}
template<class T>
void g() {
if constexpr (false)
static_assert(false); // ill-formed; no
// diagnostic required for template definition
}
I take it that it's completely forbidden to use static_assert inside constexpr if (at least the false / non-taken branch, but that in practice means it's not a safe or useful thing to do).
How does this come about from the standard text? I find no mentioning of static_assert in the proposal wording, and C++14 constexpr functions do allow static_assert (details at cppreference: constexpr).
Is it hiding in this new sentence (after 6.4.1) ? :
When a constexpr if statement appears in a templated entity,
during an instantiation of the enclosing template or generic lambda,
a discarded statement is not instantiated.
From there on, I assume that it is also forbidden, no diagnostic required, to call other constexpr (template) functions which somewhere down the call graph may call static_assert.
Bottom line:
If my understanding is correct, doesn't that put a quite hard limit on the safety and usefulness of constexpr if as we would have to know (from documentation or code inspection) about any use of static_assert? Are my worries misplaced?
Update:
This code compiles without warning (clang head 3.9.0) but is to my understanding ill-formed, no diagnostic required. Valid or not?
template< typename T>
constexpr void other_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template<class T>
void g() {
if constexpr (false)
other_library_foo<T>();
}
int main(){
g<float>();
g<int>();
}
This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }. [temp.res]/8 with the new change bolded:
The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a substatement
of a constexpr if statement ([stmt.if]) within a
template and the template is not instantiated, or
[...]
No valid specialization can be generated for a template containing static_assert whose condition is nondependent and evaluates to false, so the program is ill-formed NDR.
static_asserts with a dependent condition that can evaluate to true for at least one type are not affected.
C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with a bool template non-type parameter that we use to trigger the static_assert. We immediately invoke the lambda with (), but since the lambda won't be instantiated if its else branch is not taken, the assertion will not trigger unless that else is actually taken:
template<typename T>
void g()
{
if constexpr (case_1)
// ...
else if constexpr (case_2)
// ...
else
[]<bool flag = false>()
{static_assert(flag, "no match");}();
}
Edit: I'm keeping this self-answer with examples and more detailed explanations of the misunderstandings that lead to this questions. The short answer by T.C. is strictly enough.
After rereading the proposal and on static_assert in the current draft, and I conclude that my worries were misguided. First of all, the emphasis here should be on template definition.
ill-formed; no diagnostic required for template definition
If a template is instantiated, any static_assert fire as expected. This presumably plays well with the statement I quoted:
... a discarded statement is not instantiated.
This is a bit vague to me, but I conclude that it means that templates occurring in the discarded statement will not be instantiated. Other code
however must be syntactically valid. A static_assert(F), [where F is false, either literally or a constexpr value] inside a discarded if constexpr clause will thus still 'bite' when the template containing the static_assert is instantiated. Or (not required, at the mercy of the compiler) already at declaration if it's known to always be false.
Examples: (live demo)
#include <type_traits>
template< typename T>
constexpr void some_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template< typename T>
constexpr void other_library_bar(){
static_assert(std::is_same<T,float>::value);
}
template< typename T>
constexpr void buzz(){
// This template is ill-formed, (invalid) no diagnostic required,
// since there are no T which could make it valid. (As also mentioned
// in the answer by T.C.).
// That also means that neither of these are required to fire, but
// clang does (and very likely all compilers for similar cases), at
// least when buzz is instantiated.
static_assert(! std::is_same<T,T>::value);
static_assert(false); // does fire already at declaration
// with latest version of clang
}
template<class T, bool IntCase>
void g() {
if constexpr (IntCase){
some_library_foo<T>();
// Both two static asserts will fire even though within if constexpr:
static_assert(!IntCase) ; // ill-formed diagnostic required if
// IntCase is true
static_assert(IntCase) ; // ill-formed diagnostic required if
// IntCase is false
// However, don't do this:
static_assert(false) ; // ill-formed, no diagnostic required,
// for the same reasons as with buzz().
} else {
other_library_bar<T>();
}
}
int main(){
g<int,true>();
g<float,false>();
//g<int,false>(); // ill-formed, diagnostic required
//g<float,true>(); // ill-formed, diagnostic required
}
The standard text on static_assert is remarkably short. In standardese, it's a way to make the program ill-formed with diagnostic (as #immibis also pointed out):
7.6 ... If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and
the resulting diagnostic message (1.4) shall include the text of the
string-literal, if one is supplied ...
The most concise way I've come across to work-around this (at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:
template<class T>
void g() {
if constexpr (can_use_it_v<T>) {
// do stuff
} else {
// can't use 'false' -- expression has to depend on a template parameter
static_assert(!sizeof(T*), "T is not supported");
}
}
The point of using T* is to still give the proper error for incomplete types.
I also came across this discussion in the old isocpp mailing list which may add to this discussion. Someone there brings up the interesting point that doing this kind of conditional static_assert is not always the best idea, since it cannot be used to SFINAE-away overloads, which is sometimes relevant.
Your self-answer and possibly the one by T.C. are not quite correct.
First of all, the sentence "Both two static asserts will fire even though within if constexpr" is not correct. They won't because the if constexpr condition depends on a template parameter.
You can see that if you comment out the static_assert(false) statements and the definition of buzz() in your example code: static_assert(!IntCase) won't fire and it will compile.
Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T> are allowed (and have no effect) inside a discarded constexpr if, even if there's no T for which they evaluate to true.
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). It should say "could be generated", with further clarification of what is meant by "could".
This is related to the question whether AlwaysFalse<T>::value and ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about).
I would argue that they are, since it's "can" and not "could" and both are false for all types at the point of their instantiation.
The crucial difference between std::is_same and the non-standard wrapper here is that the latter could theoretically be specialized (thanks, cigien, for pointing this out and providing the link).
The question whether ill-formed NDR or not also crucially depends on whether the template is instantiated or not, just to make that entirely clear.
My solution is:
if constexpr (is_same_v<T,int>)
// ...
else if constexpr (is_same_v<T,float>)
// ...
else
static_assert(std::is_same_v<T, void> && !std::is_same_v<T, void>, "Unsupported element type.");
P0292R1 constexpr if has been included, on track for C++17. It seems useful (and can replace use of SFINAE), but a comment regarding static_assert being ill-formed, no diagnostic required in the false branch scares me:
Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.
void f() {
if constexpr (false)
static_assert(false); // ill-formed
}
template<class T>
void g() {
if constexpr (false)
static_assert(false); // ill-formed; no
// diagnostic required for template definition
}
I take it that it's completely forbidden to use static_assert inside constexpr if (at least the false / non-taken branch, but that in practice means it's not a safe or useful thing to do).
How does this come about from the standard text? I find no mentioning of static_assert in the proposal wording, and C++14 constexpr functions do allow static_assert (details at cppreference: constexpr).
Is it hiding in this new sentence (after 6.4.1) ? :
When a constexpr if statement appears in a templated entity,
during an instantiation of the enclosing template or generic lambda,
a discarded statement is not instantiated.
From there on, I assume that it is also forbidden, no diagnostic required, to call other constexpr (template) functions which somewhere down the call graph may call static_assert.
Bottom line:
If my understanding is correct, doesn't that put a quite hard limit on the safety and usefulness of constexpr if as we would have to know (from documentation or code inspection) about any use of static_assert? Are my worries misplaced?
Update:
This code compiles without warning (clang head 3.9.0) but is to my understanding ill-formed, no diagnostic required. Valid or not?
template< typename T>
constexpr void other_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template<class T>
void g() {
if constexpr (false)
other_library_foo<T>();
}
int main(){
g<float>();
g<int>();
}
This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }. [temp.res]/8 with the new change bolded:
The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a substatement
of a constexpr if statement ([stmt.if]) within a
template and the template is not instantiated, or
[...]
No valid specialization can be generated for a template containing static_assert whose condition is nondependent and evaluates to false, so the program is ill-formed NDR.
static_asserts with a dependent condition that can evaluate to true for at least one type are not affected.
C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with a bool template non-type parameter that we use to trigger the static_assert. We immediately invoke the lambda with (), but since the lambda won't be instantiated if its else branch is not taken, the assertion will not trigger unless that else is actually taken:
template<typename T>
void g()
{
if constexpr (case_1)
// ...
else if constexpr (case_2)
// ...
else
[]<bool flag = false>()
{static_assert(flag, "no match");}();
}
Edit: I'm keeping this self-answer with examples and more detailed explanations of the misunderstandings that lead to this questions. The short answer by T.C. is strictly enough.
After rereading the proposal and on static_assert in the current draft, and I conclude that my worries were misguided. First of all, the emphasis here should be on template definition.
ill-formed; no diagnostic required for template definition
If a template is instantiated, any static_assert fire as expected. This presumably plays well with the statement I quoted:
... a discarded statement is not instantiated.
This is a bit vague to me, but I conclude that it means that templates occurring in the discarded statement will not be instantiated. Other code
however must be syntactically valid. A static_assert(F), [where F is false, either literally or a constexpr value] inside a discarded if constexpr clause will thus still 'bite' when the template containing the static_assert is instantiated. Or (not required, at the mercy of the compiler) already at declaration if it's known to always be false.
Examples: (live demo)
#include <type_traits>
template< typename T>
constexpr void some_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template< typename T>
constexpr void other_library_bar(){
static_assert(std::is_same<T,float>::value);
}
template< typename T>
constexpr void buzz(){
// This template is ill-formed, (invalid) no diagnostic required,
// since there are no T which could make it valid. (As also mentioned
// in the answer by T.C.).
// That also means that neither of these are required to fire, but
// clang does (and very likely all compilers for similar cases), at
// least when buzz is instantiated.
static_assert(! std::is_same<T,T>::value);
static_assert(false); // does fire already at declaration
// with latest version of clang
}
template<class T, bool IntCase>
void g() {
if constexpr (IntCase){
some_library_foo<T>();
// Both two static asserts will fire even though within if constexpr:
static_assert(!IntCase) ; // ill-formed diagnostic required if
// IntCase is true
static_assert(IntCase) ; // ill-formed diagnostic required if
// IntCase is false
// However, don't do this:
static_assert(false) ; // ill-formed, no diagnostic required,
// for the same reasons as with buzz().
} else {
other_library_bar<T>();
}
}
int main(){
g<int,true>();
g<float,false>();
//g<int,false>(); // ill-formed, diagnostic required
//g<float,true>(); // ill-formed, diagnostic required
}
The standard text on static_assert is remarkably short. In standardese, it's a way to make the program ill-formed with diagnostic (as #immibis also pointed out):
7.6 ... If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and
the resulting diagnostic message (1.4) shall include the text of the
string-literal, if one is supplied ...
The most concise way I've come across to work-around this (at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:
template<class T>
void g() {
if constexpr (can_use_it_v<T>) {
// do stuff
} else {
// can't use 'false' -- expression has to depend on a template parameter
static_assert(!sizeof(T*), "T is not supported");
}
}
The point of using T* is to still give the proper error for incomplete types.
I also came across this discussion in the old isocpp mailing list which may add to this discussion. Someone there brings up the interesting point that doing this kind of conditional static_assert is not always the best idea, since it cannot be used to SFINAE-away overloads, which is sometimes relevant.
Your self-answer and possibly the one by T.C. are not quite correct.
First of all, the sentence "Both two static asserts will fire even though within if constexpr" is not correct. They won't because the if constexpr condition depends on a template parameter.
You can see that if you comment out the static_assert(false) statements and the definition of buzz() in your example code: static_assert(!IntCase) won't fire and it will compile.
Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T> are allowed (and have no effect) inside a discarded constexpr if, even if there's no T for which they evaluate to true.
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). It should say "could be generated", with further clarification of what is meant by "could".
This is related to the question whether AlwaysFalse<T>::value and ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about).
I would argue that they are, since it's "can" and not "could" and both are false for all types at the point of their instantiation.
The crucial difference between std::is_same and the non-standard wrapper here is that the latter could theoretically be specialized (thanks, cigien, for pointing this out and providing the link).
The question whether ill-formed NDR or not also crucially depends on whether the template is instantiated or not, just to make that entirely clear.
My solution is:
if constexpr (is_same_v<T,int>)
// ...
else if constexpr (is_same_v<T,float>)
// ...
else
static_assert(std::is_same_v<T, void> && !std::is_same_v<T, void>, "Unsupported element type.");
I was playing around with C++ concepts and function overloading in a context completely removed from any templates, and stumbled upon this:
struct S
{
int mult(int x) requires (true) { return x; }
int mult(int x) requires (false) { return x * 2; }
};
int main()
{
std::cout << S{}.mult(5) << std::endl;
}
g++ 11 refuses to compile this snippet because requires-clauses may not be attached to non-template functions.
clang++ 13 is, however, fine with this snippet, but surprisingly spits out 10 instead of the 5 that I expected.
I know that the C++20 standard has only recently come out of the oven, so I completely understand that there are many issues to be worked out regarding concepts.
Disregarding the apparent uselessness of constant literal concepts, my question is: Are programs with requires-clauses which are always false ill-formed, possibly without requiring a diagnostic? Or, perhaps, is what I have not even legal C++ at all, as g++ is saying?
These are the two first examples in the "trailing requires-clause" section of the standard:
void f1(int a) requires true; // error: non-templated function
template<typename T>
auto f2(T a) -> bool requires true; // OK
Although examples given in the standard are explicitly non-normative, these make the intent abundantly clear.
Essentially, concepts can only be applied to template functions (so your code is invalid C++), but Boolean literals (e.g., requires true) are perfectly valid.
However, templates whose requirements are never satisfied (whether by a literal false in a required clause or otherwise) seem to be ill-formed, as indicated by this example,
Your snippet is not legal C++.
[dcl.decl.general]
4 The optional requires-clause ([temp.pre]) in an init-declarator or member-declarator shall be present only if the declarator declares a templated function ([dcl.fct]). When present after a declarator, the requires-clause is called the trailing requires-clause. [...]
Since there isn't a template in sight (and no room to discuss if the mult overloads are templated functions), your attempt is plain ill-formed. It's a "shall" requirement of a diagnosable rule.
So GCC is correct. Clang is doubly wrong to call a completely unintuitive overload. Though it really went of the rails when it accepted the program to being with.
As n. 1.8e9-where's-my-share m.'s answer says, concepts can only be used with templates. So your example is ill-formed.
What if a requires-clause which are always false appears in a template? According to [temp.res.general]/6.1:
The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a
substatement of a constexpr if statement within a template and the
template is not instantiated, or ...
It is still ill-formed, but no diagnostic required. Example 11 in [temp.res.general] also confirms this.
[Example 11:
template<typename T> struct S1 {
template<typename U>
requires false
struct Inner1; // ill-formed, no diagnostic required
};
template<typename T> struct S2 {
template<typename U>
requires (sizeof(T[-(int)sizeof(T)]) > 1)
struct Inner2; // ill-formed, no diagnostic required
};
The class S1<T>::Inner1 is ill-formed, no diagnostic required,
because it has no valid specializations. S2 is ill-formed, no
diagnostic required, since no substitution into the constraints of its
Inner2 template would result in a valid expression. — end example]
P0292R1 constexpr if has been included, on track for C++17. It seems useful (and can replace use of SFINAE), but a comment regarding static_assert being ill-formed, no diagnostic required in the false branch scares me:
Disarming static_assert declarations in the non-taken branch of a
constexpr if is not proposed.
void f() {
if constexpr (false)
static_assert(false); // ill-formed
}
template<class T>
void g() {
if constexpr (false)
static_assert(false); // ill-formed; no
// diagnostic required for template definition
}
I take it that it's completely forbidden to use static_assert inside constexpr if (at least the false / non-taken branch, but that in practice means it's not a safe or useful thing to do).
How does this come about from the standard text? I find no mentioning of static_assert in the proposal wording, and C++14 constexpr functions do allow static_assert (details at cppreference: constexpr).
Is it hiding in this new sentence (after 6.4.1) ? :
When a constexpr if statement appears in a templated entity,
during an instantiation of the enclosing template or generic lambda,
a discarded statement is not instantiated.
From there on, I assume that it is also forbidden, no diagnostic required, to call other constexpr (template) functions which somewhere down the call graph may call static_assert.
Bottom line:
If my understanding is correct, doesn't that put a quite hard limit on the safety and usefulness of constexpr if as we would have to know (from documentation or code inspection) about any use of static_assert? Are my worries misplaced?
Update:
This code compiles without warning (clang head 3.9.0) but is to my understanding ill-formed, no diagnostic required. Valid or not?
template< typename T>
constexpr void other_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template<class T>
void g() {
if constexpr (false)
other_library_foo<T>();
}
int main(){
g<float>();
g<int>();
}
This is talking about a well-established rule for templates - the same rule that allows compilers to diagnose template<class> void f() { return 1; }. [temp.res]/8 with the new change bolded:
The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template or a substatement
of a constexpr if statement ([stmt.if]) within a
template and the template is not instantiated, or
[...]
No valid specialization can be generated for a template containing static_assert whose condition is nondependent and evaluates to false, so the program is ill-formed NDR.
static_asserts with a dependent condition that can evaluate to true for at least one type are not affected.
C++20 makes static_assert in the else branch of if constexpr much shorter now, because it allows template lambda parameters. So to avoid the ill-formed case, we can now define a lambda with a bool template non-type parameter that we use to trigger the static_assert. We immediately invoke the lambda with (), but since the lambda won't be instantiated if its else branch is not taken, the assertion will not trigger unless that else is actually taken:
template<typename T>
void g()
{
if constexpr (case_1)
// ...
else if constexpr (case_2)
// ...
else
[]<bool flag = false>()
{static_assert(flag, "no match");}();
}
Edit: I'm keeping this self-answer with examples and more detailed explanations of the misunderstandings that lead to this questions. The short answer by T.C. is strictly enough.
After rereading the proposal and on static_assert in the current draft, and I conclude that my worries were misguided. First of all, the emphasis here should be on template definition.
ill-formed; no diagnostic required for template definition
If a template is instantiated, any static_assert fire as expected. This presumably plays well with the statement I quoted:
... a discarded statement is not instantiated.
This is a bit vague to me, but I conclude that it means that templates occurring in the discarded statement will not be instantiated. Other code
however must be syntactically valid. A static_assert(F), [where F is false, either literally or a constexpr value] inside a discarded if constexpr clause will thus still 'bite' when the template containing the static_assert is instantiated. Or (not required, at the mercy of the compiler) already at declaration if it's known to always be false.
Examples: (live demo)
#include <type_traits>
template< typename T>
constexpr void some_library_foo(){
static_assert(std::is_same<T,int>::value);
}
template< typename T>
constexpr void other_library_bar(){
static_assert(std::is_same<T,float>::value);
}
template< typename T>
constexpr void buzz(){
// This template is ill-formed, (invalid) no diagnostic required,
// since there are no T which could make it valid. (As also mentioned
// in the answer by T.C.).
// That also means that neither of these are required to fire, but
// clang does (and very likely all compilers for similar cases), at
// least when buzz is instantiated.
static_assert(! std::is_same<T,T>::value);
static_assert(false); // does fire already at declaration
// with latest version of clang
}
template<class T, bool IntCase>
void g() {
if constexpr (IntCase){
some_library_foo<T>();
// Both two static asserts will fire even though within if constexpr:
static_assert(!IntCase) ; // ill-formed diagnostic required if
// IntCase is true
static_assert(IntCase) ; // ill-formed diagnostic required if
// IntCase is false
// However, don't do this:
static_assert(false) ; // ill-formed, no diagnostic required,
// for the same reasons as with buzz().
} else {
other_library_bar<T>();
}
}
int main(){
g<int,true>();
g<float,false>();
//g<int,false>(); // ill-formed, diagnostic required
//g<float,true>(); // ill-formed, diagnostic required
}
The standard text on static_assert is remarkably short. In standardese, it's a way to make the program ill-formed with diagnostic (as #immibis also pointed out):
7.6 ... If the value of the expression when so converted is true, the declaration has no effect. Otherwise, the program is ill-formed, and
the resulting diagnostic message (1.4) shall include the text of the
string-literal, if one is supplied ...
The most concise way I've come across to work-around this (at least in current compilers) is to use !sizeof(T*) for the condition, detailed by Raymond Chen here. It's a little weird, and doesn't technically get around the ill-formed problem, but at least it's short and doesn't require including or defining anything. A small comment explaining it may assist readers:
template<class T>
void g() {
if constexpr (can_use_it_v<T>) {
// do stuff
} else {
// can't use 'false' -- expression has to depend on a template parameter
static_assert(!sizeof(T*), "T is not supported");
}
}
The point of using T* is to still give the proper error for incomplete types.
I also came across this discussion in the old isocpp mailing list which may add to this discussion. Someone there brings up the interesting point that doing this kind of conditional static_assert is not always the best idea, since it cannot be used to SFINAE-away overloads, which is sometimes relevant.
Your self-answer and possibly the one by T.C. are not quite correct.
First of all, the sentence "Both two static asserts will fire even though within if constexpr" is not correct. They won't because the if constexpr condition depends on a template parameter.
You can see that if you comment out the static_assert(false) statements and the definition of buzz() in your example code: static_assert(!IntCase) won't fire and it will compile.
Furthermore, things like AlwaysFalse<T>::value or ! std::is_same_v<T, T> are allowed (and have no effect) inside a discarded constexpr if, even if there's no T for which they evaluate to true.
I think that "no valid specialization can be generated" is bad wording in the standard (unless cppreference is wrong; then T.C. would be right). It should say "could be generated", with further clarification of what is meant by "could".
This is related to the question whether AlwaysFalse<T>::value and ! std::is_same_v<T, T> are equivalent in this context (which is what the comments to this answer are about).
I would argue that they are, since it's "can" and not "could" and both are false for all types at the point of their instantiation.
The crucial difference between std::is_same and the non-standard wrapper here is that the latter could theoretically be specialized (thanks, cigien, for pointing this out and providing the link).
The question whether ill-formed NDR or not also crucially depends on whether the template is instantiated or not, just to make that entirely clear.
My solution is:
if constexpr (is_same_v<T,int>)
// ...
else if constexpr (is_same_v<T,float>)
// ...
else
static_assert(std::is_same_v<T, void> && !std::is_same_v<T, void>, "Unsupported element type.");