Can a concept evaluation depend on where it is evaluated? - c++

[temp.concept]/5 says:
A concept is not instantiated ([temp.spec]).
[ Note: An id-expression that denotes a concept specialization is evaluated as an expression ([expr.prim.id]). [...]]
Does it mean that this rule bellow ([temp.point]/8) does not apply?
If two different points of instantiation give a template specialization different meanings according to the one-definition rule, the program is ill-formed, no diagnostic required.
For example if this rule does not apply, this code bellow is well formed:
template<class T>
concept Complete = sizeof(T)==sizeof(T);
struct A;
constexpr inline bool b1 = Complete<A>; //Complete<A>==false;
struct A{};
constexpr inline bool b2 = Complete<A>; //Complete<A>==true;
This question is followed by this one

Can a concept evaluation depend on where it is evaluated?
No.
It used to be the case that this was true (as my answer before this edit stated), but it turns out that this severely inhibits compiler throughput (since you cannot cache the result of a concept check) and the motivation for having it to begin with was pretty weak. This was a very late change, adopted as part of P2104 in the Prague 2020 meeting which adds the following sentence to [temp.constr.atomic]:
If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.
As a result, this:
template<class T>
concept Complete = sizeof(T) == sizeof(T);
struct A;
static_assert(!Complete<A>);
struct A {};
static_assert(Complete<A>);
is ill-formed, NDR (practically speaking, Complete<A> will still be false after A becomes complete). In other words, we "memoize" concepts in the same way we "memoize" template instantiations.

Related

Why is substitution failure in parameter mappings considered ill-formed?

In this code,
template<class T, class U>
concept always_true = true;
template<class T>
concept always_true_if_tagged = always_true<T, typename T::tag>;
struct A {
using tag = int;
};
static_assert(always_true_if_tagged<A>);
static_assert(!always_true_if_tagged<int>); //GCC says this failed
GCC says that the second assertion failed. Both Clang and MSVC are agree to compile it.
I originally thought that it is ill-form with no diagnostic required, because of temp.constr.normal#1.4
The normal form of a concept-id C<A1, A2, ..., An> is the normal form of the constraint-expression of C, after substituting A1, A2, ..., An for C's respective template parameters in the parameter mappings in each atomic constraint. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.
The substitution T::typename tag is a parameter mapping for always_true, so it is ill-formed; no diagnostic is required.
So my first two questions are
Was I correct? (Is it ill-formed and did I cite the correct reason?)
Why it should be ill-formed? (If I was correct.)
One of the solutions is to check the nested typename before. So the parameter mapping for always_true doesn't happen.
template<class T>
concept always_true_if_tagged =
requires {typename T::tag;}
&& always_true<T, typename T::tag>;
Furthermore, temp.constr.atomic#3 says
To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.
If I write
template<class T>
concept always_true_if_tagged = bool(always_true<T, typename T::tag>);
bool(always_true<T, typename T::tag>) is an atomic constraint IIUC. The substitution of T::typename tag with T=int results in invalid type, so it should be well-formed and not satisfied.
So my last two(or four) questions are
Why this doesn't apply to the first code or why [temp.constr.normal#1.4] doesn't apply here?
3.1. Is this substitution a parameter mapping for concept-id always_true?
3.2. Is the usage of always_true in concept C = always_true<T, typename T::tag> an atomic contraint? temp.constr.constr#general-1 says there are three different kinds of constraints: conjunctions, disjunctions and atomic constraints.
Why can't concept C = always_true<T, typename T::tag> with T=int be well-formed like this one? (Likely the same as the second question though)
First of all, the lhs of scope resolution operator (::) can only be "a namespace or a class, enumeration, or dependent type", ill-formed otherwise.
With my experiment, always_true_if_tagged<int> is actually evaluate to true in gcc compiler, so the static_assert failed is because of evaluating to false. I guess that gcc compiler assumed that regardless of T, U, always_true is always true, so optimized out the evaluation of typename T::tag in constraint normalization.
So my answers to your questions:
I believe that you are correct.
The compiler has no idea whether the constraint is satisfied in this state (constraint normalization), and cannot perform further evaluation until a concept returns true or false.
As descibe above, I think compiler optimzed out the evaluation.
Subtitution is happend when concept is used, and parameter mapping is "created" when concept is declared, see follow.
Atomic constraint
An atomic constraint consists of an expression E and a mapping from the template parameters that appear within E to template arguments involving the template parameters of the constrained entity, called its parameter mapping.
I believe you have the answer now.
Were you correct? […]
Yes typename T::tag doesn’t name a valid type with [T = int].
Why should it be ill-formed?
The reason you cited is right. typename int::tag is just non-sense.
From memory, you’d also need to move the template specialization below the struct A to use typename A::tag in the template.
Why […]
I don’t believe this is well-formed because of §7.3.2 [conv.lval]
I don’t have the answer to the questions, only more questions.
In a concept, it’s not a template instantiation (See: [temp.concept], [temp.spec], [temp.dep]), so I don’t understand how that could be an atomic constraint (is it a constant expression? How can it evaluate to anything).
clang 14.0.0 doesn’t generate warnings with -fsanitize=undefined on 😰
See also:
SFINAE

if constexpr behavior differs between C++17 and C++20 [duplicate]

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.");

Functional equivalency in template constraints vs member function constraints

The latest standard draft N4910 has this example in [temp.over.link] regarding functional equivalence:
template<int I> concept C = true;
template<typename T> struct A {
void f() requires C<42>; // #1
void f() requires true; // OK, different functions
};
My understanding is that this is ok, since C<42> and true are unevaluated operands. Therefore according to [temp.over.link]/5 when considering whether the constraints are functionally equivalent, not the result of the expressions, but what operations are performed on which entity and in what order, is deciding functional equivalency of the constraints.
However if the constraints were functionally equivalent, then because they are not equivalent, by [temp.over.link]/7 the program would be ill-formed, no diagnostic required, as declaring the same member twice would make the program ill-formed.
On the other hand
template<typename>
requires C<42>
void g() {};
template<typename>
requires true
void g() {};
seems to be ill-formed, no diagnostic required, because [temp.over.link]/6 says that template heads are functionally equivalent if they accept and satisfy the same template arguments.
Am I misunderstanding the example and referenced standard wording or is there really such a difference? If so, why?

Are concepts with only a boolean literal value ill-formed, no diagnostic required?

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]

Is this use of static_assert inside if constexpr well-formed?

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