2

I have the following sample code

template<class T1, class T2>
class Operation
{
public:
    constexpr Operation(const T1& lhs, const T2& rhs) noexcept
        : m_lhs(lhs), m_rhs(rhs) { }

private:
    const T1& m_lhs;
    const T2& m_rhs;
};

int main()
{
    constexpr int a = 3;
    constexpr int b = 4;

    constexpr Operation op(a, b);

    return 0;
}

Compiling this with cygwin (gcc 8.2) I get

error: 'Operation<int, int>{a, b}' is not a constant expression:
       constexpr Operation op(a, b);

With MSVC 2019 it compiles fine, but IntelliSense ironically underlines the a in op(a, b) with the tooltip "expression must have a constant value".

Any advice as to what the issue is, and how to fix it?

melpomene
  • 84,125
  • 8
  • 85
  • 148
Phil-ZXX
  • 2,359
  • 2
  • 28
  • 40

1 Answers1

5

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.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 2
    You don't think that this is absurdly arcane and esoteric for what is supposed to be a general-purpose programming language developed by a group who's currently trying to broaden its appeal? – Lightness Races in Orbit Aug 18 '19 at 14:58
  • 1
    @LightnessRacesinOrbit How do you expect taking the address of an object to work during constant evaluation if its address isn't constant? Also this has been the rule since C++11, so it's not like... new. – Barry Aug 18 '19 at 14:59
  • 2
    I'm not saying it doesn't have a technical reason, or that it is a "new". I'm saying it's absurdly arcane and esoteric. People (who are not language lawyers like yourself) should not have to wonder about stuff like this. Hence an apparently reasonable and simple program in the question that required posting on Stack Overflow to actually make work. At the very least, the diagnostic is borderline useless. – Lightness Races in Orbit Aug 18 '19 at 15:02
  • @LightnessRacesinOrbit So what you're saying is: a better diagnostic would completely resolve the problem. – Barry Aug 18 '19 at 15:06
  • No. What I'm saying is exactly what I said. Why do you keep responding to things that I haven't said?! – Lightness Races in Orbit Aug 18 '19 at 15:07
  • @LightnessRacesinOrbit Because I'm trying to find an actionable point. – Barry Aug 18 '19 at 15:09
  • @LightnessRacesinOrbit Well, I'm sorry for trying to take steps to improve things anyway. Submitted [91483](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91483). – Barry Aug 18 '19 at 15:12
  • @LightnessRacesinOrbit Sorry but Barry is right here. If you cannot propose how the language could solve it better then complaining at the current solution is not constructive. If explicitly requested from the language that some value has to be know compile-time (`constexpr`) and then you requested that it has to be generated from data that can only be know at run-time then what do you expect? An error is the only possible outcome. I guess, the error message could be better so people don't have to ask on Stackoverflow. This one isn't so terrible, though. – Piotr Siupa Aug 18 '19 at 17:53
  • @LightnessRacesinOrbit maybe the wording in the standard is complex, but the idea is simple (and not arcane or esoteric): compile-time evaluation can't depend on the address of stack variables – M.M Aug 19 '19 at 00:59
  • 1
    @M.M. Doesn't help that some of the comments have been silently removed. The context of my original observation has been completely lost. I was not at all complaining about the answer or about the question. I merely stated that in my opinion this is a perfect example of why C++ is now "terrible" (read: higher barrier to entry than is really necessary) – Lightness Races in Orbit Aug 19 '19 at 10:31