Constexpr Class Taking Const References Not Compiling

Constexpr Class taking const references not compiling

Yeah, this rule is one of the more complex ones as far as constant evaluation is concerned.

Basically, you cannot have a constexpr reference to an object that doesn't have static storage duration. Taking a reference to an object is basically copying its address - and in order for an object's address to be a constant expression, the address itself needs to be constant - so it has to persist. That is, it needs to be static.

So if you change the things you're referring to to have static storage duration instead, everything works:

static constexpr int a = 3;
static constexpr int b = 4;

constexpr Operation op(a, b); // now ok

The specific rule your program violates is [expr.const]/10, and T.C. helped me understand how it applies. Declaring a constexpr variable requires the initialization to be a constant expression ([dcl.constexpr/10]):

In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression.

We don't say this, but it makes sense and certainly helps resolve this particular situation, but the "full-expression of the initialization" can be interpreted as a prvalue -- since a prvalue is an expression whose evaluation initializes an object ([basic.lval]/1).

Now, [expr.const]/10 reads:

A constant expression is either a glvalue core constant expression [...], or or a prvalue core constant expression whose value satisfies the following constraints:

  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
  • [...],
  • if the value is an object of class or array type, each subobject satisfies these constraints for the value.

An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.

The initialization Operation(a, b) is a prvalue, so we need each reference data member to refer to an entity that is permitted as a result of a constant expression. Our reference data members refer to a and b, neither of which has static storage duration nor are temporaries nor are non-immediate functions. Hence, the overall initialization isn't a constant expression, and is ill-formed.

Making a and b static gives them static storage duration, which makes them permitted results of constant expressions, which makes the prvalue initialization satisfy all the requirements, which makes the declaration of op valid.


This is all a long winded way of saying: when dealing with constant evaluation, everything everywhere has to be constant all the way down. Some of our ways of wording this are very complex (like this one), but it's based on the fundamental idea that the model of constant evaluation is basically like pausing evaluating the code to go run a separate program to produce an answer. Producing op requires these addresses to be known, fixed things - and that only happens for static storage duration.

Why passing constexpr object by const reference works, but by value doesn't compile

You are missing an initializer on iseq. You have to add it:

constexpr std::integer_sequence<int, 1,2,3,4> iseq{};
^^

From [dcl.constexpr]:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have
literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.20). Otherwise, or if a constexpr specifier is used in a reference declaration, every fullexpression
that appears in its initializer shall be a constant expression. [ Note: Each implicit conversion
used in converting the initializer expressions and each constructor call used for the initialization is part of
such a full-expression. —end note ]
[ Example:

struct pixel {
int x, y;
};
constexpr pixel ur = { 1294, 1024 }; // OK
constexpr pixel origin; // error: initializer missing

—end example ]

Moreover, as Columbo suggests in his comment and answer, simply being initialized is insufficent. A user-provided constructor is also required, as per [dcl.init]:

If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type
with a user-provided default constructor.

It's a little odd to have the most relevant section (dcl.constexpr) have an incomplete description of the requirements for a constepxr object declaration.

Why is a constexpr function on a reference not constexpr?

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.

Reference fields in constexpr class

Your problem can be reduced to this:

#include <array>

struct X
{
constexpr X() = default;

std::array<int, 2> data{};

int& x { this->data[0]};
};

int main()
{
constexpr auto res = X{};
}
<source>:14:20: error: constexpr variable 'res' must be initialized by a constant expression

constexpr auto res = X{};

^ ~~~

<source>:14:20: note: reference to subobject of 'res' is not a constant expression

<source>:14:20: note: declared here

The issue is that a constexpr reference can be bound only to objects with static storage duration (which this is not). See how to initialize a constexpr reference

So afaik it is not possible without making it a method (the x() syntax).

Compiler can't execute constexpr expression

I rewrite the code that will be work and I think will be execute in compile time:

template<typename ... Args>
constexpr auto make_generic_header(const Args ... args) {
std::integral_constant<size_t, sizeof...(Args)> header_lenght;
return header_lenght.value;
}

constexpr auto create_ipv4_header() {
constexpr auto x = make_generic_header(0b01, 0b10, 0b01);
return x;
}

I removed function get_init_size and used code part of template parameter(It is guaranteed that it is will be execute on compile time) and after return the number of arguments passed to function(For std::integral_constant for all object value same and know on compile time)

Strange behavior with constexpr interacting with const references

The rule you're violating here:

constexpr X a(x);

Is that a consetxpr pointer or a constexpr reference has to refer to an object with static storage duration - that's the only way for its address to itself be a constant expression. That is not the case with x.

But once you make it so (the const was redundant):

static constexpr int x = 10;

then the rest works:

constexpr X a(x);
static_assert(f(a).Get() == 10, "This should work.");

The specific rule is [expr.const]/11:

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:

  • if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,

[...]

An entity is a permitted result of a constant expression if it is an object with static storage duration that either is not a temporary object or is a temporary object whose value satisfies the above constraints, or if it is a non-immediate function.


See also these answers of mine. I should probably just close all of these as dupes of one of these?

constexpr result from non-constexpr call

As mentioned in the comments, the rules for constant expressions do not generally require that every variable mentioned in the expression and whose lifetime began outside the expression evaluation is constexpr.

There is a (long) list of requirements that when not satisfied prevent an expression from being a constant expression. As long as none of them is violated, the expression is a constant expression.

The requirement that a used variable/object be constexpr is formally known as the object being usable in constant expressions (although the exact definition contains more detailed requirements and exceptions, see also linked cppreference page).

Looking at the list you can see that this property is required only in certain situations, namely only for variables/objects whose lifetime began outside the expression and if either a virtual function call is performed on it, a lvalue-to-rvalue conversion is performed on it or it is a reference variable named in the expression.

Neither of these cases apply here. There are no virtual functions involved and a is not a reference variable. Typically the lvalue-to-rvalue conversion causes the requirement to become important. An lvalue-to-rvalue conversions happens whenever you try to use the value stored in the object or one of its subobjects. However A is an empty class without any state and therefore there is no value to read. When passing a to the function, the implicit copy constructor is called to construct the parameter of f, but because the class is empty, it doesn't actually do anything. It doesn't access any state of a.

Note that, as mentioned above, the rules are stricter if you use references, e.g.

A a;
A& ar = a;
constexpr int kInt = f(ar);

will fail, because ar names a reference variable which is not usable in constant expressions. This will hopefully be fixed soon to be more consistent. (see https://github.com/cplusplus/papers/issues/973)

Why can I not use a constexpr global variable to initialize a constexpr reference type?

Compile adding const after int.

constexpr int const & k = r ;
// ...........^^^^^

The problem is that constepxr implies const, so when you define r

constexpr int r =100;

you define constexpr as an int const value (also take into account that const is applied to the type on the left; on the right only if there isn't a type on the left; so const int and int const are the same thing).

But your k

constexpr int & k = r ;

isn't a const (implied by constexpr) reference to an int const but only a const reference to an int.

And you can't initialize a reference to an int variable with an int const value.

You can solve the error by making k a const reference to an int const.



Related Topics



Leave a reply



Submit