Setting local to parameter allows use as constexpr? [duplicate] - c++

Consider the following function:
template <size_t S1, size_t S2>
auto concatenate(std::array<uint8_t, S1> &data1, std::array<uint8_t, S2> &data2) {
std::array<uint8_t, data1.size() + data2.size()> result;
auto iter = std::copy(data1.begin(), data1.end(), result.begin());
std::copy(data2.begin(), data2.end(), iter);
return result;
}
int main()
{
std::array<uint8_t, 1> data1{ 0x00 };
std::array<uint8_t, 1> data2{ 0xFF };
auto result = concatenate(data1, data2);
return 0;
}
When compiled using clang 6.0, using -std=c++17, this function does not compile, because the size member function on the array is not constexpr due to it being a reference. The error message is this:
error: non-type template argument is not a constant expression
When the parameters are not references, the code works as expected.
I wonder why this would be, as the size() actually returns a template parameter, it could hardly be any more const. Whether the parameter is or is not a reference shouldn't make a difference.
I know I could of course use the S1 and S2 template parameters, the function is merely a short illustration of the problem.
Is there anything in the standard? I was very surprised to get a compile error out of this.

Because you have evaluated a reference. From [expr.const]/4:
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 usable in constant expressions or
its lifetime began within the evaluation of e;
...
Your reference parameter has no preceding initialization, so it cannot be used in a constant expression.
You can simply use S1 + S2 instead here.

There has been a bug reported on this issue for clang titled: Clang does not allow to use constexpr type conversion in non-type template argument.
The discussion in it points that this is not really a bug.
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;
[...]
The above quote is from [expr.const]/2.11 of draft n4659 with emphasis added.

Unfortunately, the standard states that in a class member access expression The postfix expression before the dot or arrow is evaluated;63 [expr.ref]/1. A postfix expression is a in a.b. The note is really interesting because this is precisely the case here:
63) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.
So data is evaluated even if it would not be necessary and the rule fore constant expression applies on it too.

Related

clang9(or trunk) vs gcc9: member array size as non-type template parameter [duplicate]

Consider the following function:
template <size_t S1, size_t S2>
auto concatenate(std::array<uint8_t, S1> &data1, std::array<uint8_t, S2> &data2) {
std::array<uint8_t, data1.size() + data2.size()> result;
auto iter = std::copy(data1.begin(), data1.end(), result.begin());
std::copy(data2.begin(), data2.end(), iter);
return result;
}
int main()
{
std::array<uint8_t, 1> data1{ 0x00 };
std::array<uint8_t, 1> data2{ 0xFF };
auto result = concatenate(data1, data2);
return 0;
}
When compiled using clang 6.0, using -std=c++17, this function does not compile, because the size member function on the array is not constexpr due to it being a reference. The error message is this:
error: non-type template argument is not a constant expression
When the parameters are not references, the code works as expected.
I wonder why this would be, as the size() actually returns a template parameter, it could hardly be any more const. Whether the parameter is or is not a reference shouldn't make a difference.
I know I could of course use the S1 and S2 template parameters, the function is merely a short illustration of the problem.
Is there anything in the standard? I was very surprised to get a compile error out of this.
Because you have evaluated a reference. From [expr.const]/4:
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 usable in constant expressions or
its lifetime began within the evaluation of e;
...
Your reference parameter has no preceding initialization, so it cannot be used in a constant expression.
You can simply use S1 + S2 instead here.
There has been a bug reported on this issue for clang titled: Clang does not allow to use constexpr type conversion in non-type template argument.
The discussion in it points that this is not really a bug.
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;
[...]
The above quote is from [expr.const]/2.11 of draft n4659 with emphasis added.
Unfortunately, the standard states that in a class member access expression The postfix expression before the dot or arrow is evaluated;63 [expr.ref]/1. A postfix expression is a in a.b. The note is really interesting because this is precisely the case here:
63) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.
So data is evaluated even if it would not be necessary and the rule fore constant expression applies on it too.

Why is a constexpr function on a reference not constexpr?

Consider the following function:
template <size_t S1, size_t S2>
auto concatenate(std::array<uint8_t, S1> &data1, std::array<uint8_t, S2> &data2) {
std::array<uint8_t, data1.size() + data2.size()> result;
auto iter = std::copy(data1.begin(), data1.end(), result.begin());
std::copy(data2.begin(), data2.end(), iter);
return result;
}
int main()
{
std::array<uint8_t, 1> data1{ 0x00 };
std::array<uint8_t, 1> data2{ 0xFF };
auto result = concatenate(data1, data2);
return 0;
}
When compiled using clang 6.0, using -std=c++17, this function does not compile, because the size member function on the array is not constexpr due to it being a reference. The error message is this:
error: non-type template argument is not a constant expression
When the parameters are not references, the code works as expected.
I wonder why this would be, as the size() actually returns a template parameter, it could hardly be any more const. Whether the parameter is or is not a reference shouldn't make a difference.
I know I could of course use the S1 and S2 template parameters, the function is merely a short illustration of the problem.
Is there anything in the standard? I was very surprised to get a compile error out of this.
Because you have evaluated a reference. From [expr.const]/4:
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 usable in constant expressions or
its lifetime began within the evaluation of e;
...
Your reference parameter has no preceding initialization, so it cannot be used in a constant expression.
You can simply use S1 + S2 instead here.
There has been a bug reported on this issue for clang titled: Clang does not allow to use constexpr type conversion in non-type template argument.
The discussion in it points that this is not really a bug.
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;
[...]
The above quote is from [expr.const]/2.11 of draft n4659 with emphasis added.
Unfortunately, the standard states that in a class member access expression The postfix expression before the dot or arrow is evaluated;63 [expr.ref]/1. A postfix expression is a in a.b. The note is really interesting because this is precisely the case here:
63) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.
So data is evaluated even if it would not be necessary and the rule fore constant expression applies on it too.

Why references can't be used with compile time functions?

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.

Using an enum as a constant expression. Which compiler is right?

The following code uses enum member m as a constant expression, i.e. as a template parameter. The code compiles under gcc but not under clang (live demo). Clang says "error: non-type template argument is not a constant expression".
The problem can be solved by exchanging line // 1 by A<tst<p>::m> a. Therefore, my question is not how to fix this issue but which compiler is right.
template<size_t n> struct A{};
template<size_t n>
struct tst
{ enum : size_t { m= n % 15 };
template<size_t p>
void
call( tst<p> const &t2 ) {
A<t2.m> a; // 1
}
};
According to the Standard, Clang is right to reject the code.
t2.m is a class member access expression. [expr.ref]/1 says:
[...] The postfix expression before the dot or arrow is evaluated; the
result of that evaluation, together with the id-expression,
determines the result of the entire postfix expression.
There's also a note:
If the class member access expression is evaluated, the subexpression
evaluation happens even if the result is unnecessary to determine the
value of the entire postfix expression, for example if the
id-expression denotes a static member.
So, the subexpression t2 is evaluated. [expr.const]/2.9 says that an expression e cannot be a core constant expression if evaluating it results in the evaluation of
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;
t2 refers to a variable of reference type that doesn't satisfy the bullets, so t2.m is not a constant expression because it isn't a core constant expression.
All quotes from N4594, the current published working draft. The text has changed slightly since C++11, but the meaning in this case is the same.

Calling a constexpr method through a reference - is the result a constant expression?

The following code
#include <array>
void foo(const std::array<int, 42> &a)
{
constexpr size_t S = a.size();
}
int main() {}
compiles fine in GCC, but fails to compile in clang with the following error message
main.cpp:5:28: error: constexpr variable 'S' must be initialized by a constant expression
constexpr size_t S = a.size();
^~~~~~~~
Meanwhile, many posts about constexpr issues on SO seem to imply that clang often has better (more pedantic?) support for constexpr. So, which compiler would be correct in this case?
Note that both compilers gladly accept the code once the reference parameter is replaced with pass-by-value parameter.
[expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
([intro.execution]), 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;
[...]
Evaluating a.size() evaluates the id-expression a, which "refers to a variable...of reference type" and has no preceding initialization. It is therefore not a core constant expression and so not a constant expression.