Consider the code below:
template<char>
struct S { };
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
It doesn't compile for ref[0] is not a constant expression.
Anyway, the code below compiles fine:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
Should both of them compile or fail to do that for more or less the same reason?
From [expr.const] we have that:
A conditional-expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expressions:
[...]
- an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
- it is initialized with a constant expression or
- its lifetime began within the evaluation of e;
Anyway, in this case, it is initialized with a constant expression and the lifetime is the same of e, thus the rule doesn't apply.
What's wrong in my reasoning?
As a side question, I would ask if it's possible to use such an array or part of it as a template argument.
This:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
is ill-formed because according to [temp.arg.nontype]:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of
the type of the template-parameter.
and from [expr.const]:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions: [...]
(2.7) — an lvalue-to-rvalue conversion (4.1) unless it is applied to
(2.7.1) — a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const
object with a preceding initialization, initialized with a constant expression, or
(2.7.2) — a non-volatile glvalue that refers to a subobject of a string literal (2.13.5), or
(2.7.3) — a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to
a non-mutable sub-object of such an object, or
(2.7.4) — a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within
the evaluation of e;
ref[0] requires an lvalue-to-rvalue conversion and none of those sub-bullets apply. Note that ref is not a string literal, so 2.7.2 doesn't apply, nor is it defined with constexpr, because it's a function argument and we don't have that capability.
We basically need the ability to pass string literals as literals, which doesn't exist yet.
The other example:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
doesn't have the converted constant expression required - that one was brought in by the template non-type argument. This code is fine, and would only be problematic if you tried to initialize a constexpr variable with a non-constexpr array value.
The first example shouldn't compile because you cannot have compiletime-only constexpr functions (or overload on compiletime-ness, like D's __cfte).
Following this reasoning, if you called first example's f at runtime, what would its return type be?
As to the side question: Boost Hana, despite supporting only the newest standard, only uses string literals for runtime stuff, so it may not be possible.
Related
Why does it working?
#include <cstdio>
template<auto x> struct constant {
constexpr operator auto() { return x; }
};
constant<true> true_;
static constexpr const bool true__ = true;
template<auto tag> struct registryv2 {
// not constexpr
static auto push() {
fprintf(stderr, "%s\n", __PRETTY_FUNCTION__);
//*
return true_; // compiles
/*/
return true__; // read of non-const variable 'x' is not allowed in a constant expression
//*/
}
// not constexpr either
static inline auto x = push();
};
static_assert(registryv2<0>::x);
https://godbolt.org/z/GYTdE3M9q
static_assert evaluates non constant expression
Nope, it most certainly does not. Constant evaluation has a strict set of conditions to is must obey in order to succeed.
For starters:
[dcl.dcl]
6 In a static_assert-declaration, the constant-expression shall be a contextually converted constant expression of type bool.
"contextually converted" is standard lingo for "we'll consider explicit conversion operators". The place where it may become counter-intuitive is when "converted constant expression" is defined.
[expr.const]
4 A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only
user-defined conversions,
[...]
The fine point is in the first sentence of the paragraph. The converted expression must be a constant expression. But the source expression doesn't have to be! So long as the conversion sequence is limited to the list in the paragraph and is valid constant evaluation itself, we are in the clear. In your example, the expression registryv2<0>::x has type constant<true>, it can be contextually converted to a bool via the user defined conversion operator. And well, the conversion operator satisfiers all the requirements of a constexpr function and constant evaluation.
The list of requirements for constant evaluation is rather long so I won't go over it to verify every bullet is upheld. But I will demonstrate that we can trip one of them.
template<auto x> struct constant {
bool const x_ = x;
constexpr explicit operator auto() const { return x_; }
};
This change immediately makes the godbolt code sample ill-formed. Why? Because we are doing an lvalue-to-rvalue conversion (standard lingo for access) on a bool when it is not permitted.
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
Going over the exceptions, none apply. So now registryv2<0>::x is not a contextually converted constant expression of type bool.
This also explain why true__1 is verboten. Same issue, access to an object that is disallowed.
1 - That's a reserved identifier. Two consecutive underscores belong to the implementation for any use. Not critical to the issue at hand, but take note.
It works because this conversion to auto operator is constexpr.
template<auto x> struct constant {
constexpr explicit operator auto() const { return x; }
};
It is called by the static_assert, even if marked explicit. It looks like putting an expession within a static_assert is akin to making an explicit cast to bool. The same is true for 'if' as in if (registryv2<0>::x)
#include <iostream>
constexpr int func2(int const& id){
return id;
}
template<int v>
struct Test{
};
int main(){
const int v = 0;
Test<func2(v)> c;
}
Consider the above code,I just don't understand why the code is well-formed.My pointview is that the name v is used as a glvalue when evalute expression func2,becuase the parameter of func2 is of reference type,the v need to be bound to the id-expression id.So we look at the requirement of a glvalue constant expression,here are quotes about that.
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), or a prvalue core constant expression whose value satisfies the following constraints.
We ignore the case of prvalue,because here v is used as a glvalue.
An entity is a permitted result of a constant expression is:
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
In my program portion,The const int v = 0; does not have static storage duration,it just has automatic storage duration.So when evaluting the expression func2(v) to determine whether it is a constant expression,Firstly,the v must be a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression,therefore,why the program is well-formed here?If I lose any important quote,Please correct me.
We ignore the case of prvalue, because here v is used as a glvalue
Is it though? This is an example from cppreference:
void test() {
static const int a = std::random_device{}();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
const int b = 42;
constexpr const int& rb = b; // Error: b is not a glvalue constant expression
constexpr int ib = b; // OK: b is a prvalue constant expression
}
And yes, const int b = 42 is rather weird here because technically speaking, you can bind b to const int&, const_cast the const away and assign a runtime value to it. However, considering what is an integral constant expression and what are the requirements of a const object it makes perfect sense:
Integral constant expression is an expression of integral or unscoped
enumeration type implicitly converted to a prvalue, where the
converted expression is a core constant expression. If an expression
of class type is used where an integral constant expression is
expected, the expression is contextually implicitly converted to an
integral or unscoped enumeration type.
Variable b sure does look like something you could implicitly convert to a prvalue constant expression because it basically serves as an alias for literal 42 in this context and integer literals are prvalues by definition.
Now for the problematic part - this:
const object - an object whose type is const-qualified, or a
non-mutable subobject of a const object. Such object cannot be
modified: attempt to do so directly is a compile-time error, and
attempt to do so indirectly (e.g., by modifying the const object
through a reference or pointer to non-const type) results in undefined
behavior.
And:
A core constant expression is any expression whose evaluation would
not evaluate any one of the following:
...
an expression whose evaluation leads to any form of core language
undefined behavior (including signed integer overflow, division by
zero, pointer arithmetic outside array bounds, etc). Whether standard
library undefined behavior is detected is unspecified.
Means that as soon as you start doing funny things with that b, you can expect anything to happen. For example, this is what I tried doing to your code in latest MSVC with all standard-conformance options switched on:
#include <iostream>
#include <random>
constexpr int func2(int const& id) {
return id;
}
template<int v>
struct Test {
long array[v];
};
int main() {
const int v = 0;
const int& ref = v;
const_cast<int&>(ref) = std::random_device()() % std::numeric_limits<int>::max();
Test<func2(v)> c;
return 0;
}
With language extensions turned on I got a C4200: nonstandard extension used : zero-sized array in struct/union warning. After switching them off, the program wouldn't compile. And when I deleted the array part from the struct it started compiling again.
I try to answer this question.why the func2(v) is a constant expression,Becuase For expression func2(v),when evaluting this postfix-expression,there's no requirement that v must be a glvalue constant expression in the list of "would evaluate one of the following expressions:",Even,these rules does not mandate that the one expression within a potentially core constant expression would be a glvalue constant expression,only require the expression does not violate the listed requirement.So let's continue,When initialization of the parameter,It's another rule here:
A full-expression is:
[...]
an init-declarator or a mem-initializer, including the constituent expressions of the initializer
So,when evalute this full-expression,it only does not violate these listed condition,then the func2(v) would be evaluted as a constant expression,So let's look at these rules:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or,
its lifetime began within the evaluation of e;
For id-expression id,its preceding initialization is the corresponding argument,Because of this rule:
When a function is called, each parameter ([dcl.fct]) shall be initialized ([dcl.init], [class.copy], [class.ctor]) with its corresponding argument.
So,the first condition is true.And "it is initialized with a constant expression" is false,the condition "its lifetime began within the evaluation of e" is true.In conlusion,the expression func2(v) indeed a constant expression
I have two snippets.
The first snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
if constexpr (foo(a)) {
}
}
The second snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
std::string& x = a;
if constexpr (foo(x)) {
}
}
The first one compiles, but the second one does not compile (error message: error: the value of ‘x’ is not usable in a constant expression. Why? Why a is usable in a constant expression and x is not?
The command, used to compile g++ -std=c++17 main.cpp.
Because usually a constant expression cannot evaluate a reference that refers to an object with automatic storage duration. Here I mean "evaluate" by determining the identity of the object, not by determining the value of the object. So even the value of the object a is not required in your example (i.e. no lvalue-to-rvalue conversion is applied), foo(x) is still not a constant expression.
Note foo(a) does not evaluate any reference. Although the parameter of foo is a reference, it is not evaluated as an expression. In fact, even if it was evaluated, for example,
template <typename T>
constexpr bool foo(T&& t) {
t;
return false;
}
foo(a) is still a constant expression. Such cases are exceptions as the reference t is initialized within the evaluation of foo(a).
Related part in the standard (irrelevant part is elided by me):
[expr.const]/2:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
...
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
...
[expr.const]/6:
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), ...
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
Because you can't know at compile time what value x will have, all you know is that it will point to a. What you are doing is checking the "value" of x, but the value of x is the address where a is and you can't know where a will be allocated nor an address is constant.
On the other hand you already know the value of a, it's an empty std::string.
This question contains some more details: how to initialize a constexpr reference
Firstly, In your both code sections, the evaluation context all require the postfix-expression to be a constant expression. The definition of function template foo satisfies the requirements of a constexpr function due to its parameter is of literal type.
id-expression a and x are all glvalue which evaluation determine the identity of an object, but the only difference is that, In your fist code. copy-initialize the parameter from a only require identity conversion, because of the following rules:
When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion
And it does nothing, due to
[over.ics.scs#2]
As described in Clause [conv], a standard conversion sequence is either the Identity conversion by itself (that is, no conversion)
And the remain expressions are all constant expressions. So, for foo(a) this expression, it's a constant expression.
For foo(x), because x is an id-expression of reference type that subject to this rule:
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
The lifetime of x began before the evaluation of foo(x) and it isn't initialized with a constant expression because std::string a; is not a glvalue constant expression. If you modify the std::string a; to static std::string a;,then it will be ok.
The following code is accepted by GCC 7.2 and clang 5.0.0, but is rejected by Microsoft VS 2017 15.5.0 Preview 5 and Intel C++ compiler 19:
struct S { };
constexpr int f(S)
{
return 0;
}
int main()
{
auto lambda = [](auto x)
{
constexpr int e = f(x);
};
lambda(S{});
}
Microsoft:
<source>(12): error C2131: expression did not evaluate to a constant
Intel:
<source>(12): error: expression must have a constant value
constexpr int e = f(x);
^
<source>(12): note: the value of parameter "x" (declared at line 10) cannot be used as a constant
constexpr int e = f(x);
^
If I replace f(x) with f(decltype(x){}), both Microsoft and Intel do not complain. I understand that x is not a constant expression, but it is not used inside f. This is probably why GCC and clang do not complain.
I guess that Microsoft and Intel compilers are correct in rejecting this code. What do you think?
From [expr.const]:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
[...]
an lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
[...]
In f(x), we do an lvalue-to-rvalue conversion on x. x isn't of integral or enumeration type, it's not a subobject of a string-literal, it's not an object defined with constexpr, and its lifetime did not begin with the evaluation of f(x).
That seems to make this not a core constant expression.
However, as Casey points out, since S is empty, nothing in its implicitly-generated copy constructor would actually trigger this lvalue-to-rvalue conversion. That would mean that nothing in this expression actually violates any of the core constant expression restrictions, and hence gcc and clang are correct in accepting it. This interpretation seems correct to me. constexpr is fun.
This is not a gcc/clang bug. The same behavior can be reproduced in C++11 with a template function:
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
int main()
{
foo(S{});
}
on godbolt.org
The question is, given...
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
...is f(x) a constant expression?
From [expr.const]:
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:
invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor
S{} and 0 are constant expressions because it doesn't violate any of the rules in [expr.const]. f(x) is a constant expression because it's an invocation to a constexpr function.
Unless I am missing something, gcc and clang are correct here.
Is the variable v in the sample code below odr-used?
extern void* v;
template<void*&>
void f() {}
int main()
{
f<v>();
}
I found this pattern in Boost ML.
cf. http://lists.boost.org/Archives/boost/2011/04/180082.php
It says that the boost::enabler is never defined, but clang rejects it as a linkage error if -g option is provided.
cf. http://melpon.org/wandbox/permlink/nF45k7un3rFb175z
The sample code above is reduced version of the Boost ML's code and clang rejects it too.
cf. http://melpon.org/wandbox/permlink/ZwxaygXgUhbi1Cbr
I think (but I am not sure) that template non-type arguments for reference type are odr-used even if they are not referred in their template body so the Boost ML's pattern is ill-formed.
Is my understanding correct?
I believe v is odr-used. f<v> is a template-id (14.2) whose template-argument is an id-expression (5.1.1) - a form of expression. It's clearly not an unevaluated operand (it doesn't appear as an operand of typeid, sizeof, noexcept or decltype), so it's potentially evaluated per 3.2/2:
3.2/2 An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof...
At which point, we have
3.2/3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless [a condition that doesn't appear to apply here as no lvalue-to-rvalue conversion is applied].
[basic.def.odr]/3:
A variable x whose name appears as a potentially-evaluated expression
ex is odr-used by ex unless applying the lvalue-to-rvalue conversion
(4.1) to x yields a constant expression (5.19) [..]
Unfortunately, applying the l-t-r conversion to v at this point would not yield a constant expression - [expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
(1.9), would evaluate one of the following expressions: [..]
— an
lvalue-to-rvalue conversion (4.1) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding
initialization, initialized with a constant expression [..], or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
However, though the implementation suggested by Matt isn't correct, the idea certainly is. A simple way of using this approach is demonstrated in this answer, using a helper template. In your case, try
template <bool cond, int id=0>
using distinct_enable_if =
typename std::enable_if<cond, std::integral_constant<int,id>*>::type;
class test
{
public:
template< class... T,
distinct_enable_if<sizeof...(T) == 10> = nullptr>
test( T&&... ) {}
template< class T,
distinct_enable_if<std::is_arithmetic<T>{}> = nullptr>
operator T() const { return T{}; }
/* Note the additional template argument:
It ensures that the template parameter lists are not identical,
and the ODR isn't violated */
template< class T,
distinct_enable_if<std::is_pointer<T>{}, 1> = nullptr>
operator T() const { return T{}; }
};
Demo.