5
votes

My understanding is that this (nonsensical) code is not valid C++14:

class Point  {
public:
  constexpr double setX(double newX) { return x = newX; }
private:
  double x;
};

I'm trying to figure out what part of the (still officially draft) C++14 Standard disallows it. The restrictions on constexpr functions are listed in 7.1.5/2. (Sorry for the mangled formatting. I can't figure out how to beat markdown into making it look right.)

The definition of a constexpr function shall satisfy the following constraints:

  • it shall not be virtual (10.3);
  • its return type shall be a literal type;
  • each of its parameter types shall be a literal type;
  • its function-body shall be = delete, = default, or a compound-statement that does not contain
    • an asm-definition,
    • a goto statement,
    • a try-block, or
    • a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

There's nothing there that prohibits assignments to data members. There is such a prohibition in 5.19/2 (bullet 15) (again with mangled formatting, sorry):

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: [...] modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

But I don't see how 5.19 applies to 7.1.5. Can somebody clarify?

2
Why do you think it's not legal C++14?T.C.
@DietrichEpp void is a literal type. OTOH, Point is not a literal type without a constexpr constructor.T.C.
@T.C. It looks like I misinterpreted error messages from g++ and Clang. Apparently they were applying the C++11 rule that constexpr member functions are implicitly const. With the proper configuration, it looks like Clang 3.2 is happy, and that suggests that the code is valid (with the non-void return type).KnowItAllWannabe

2 Answers

5
votes

It is valid C++14. You can modify members of a literal class type as long as the lifetime of the object is contained within the evaluation of the constant expression.

The use of Point in a constant expression is controversial (CWG DR 1452), but it is allowed by current implementations. It would be a literal class except that it is not aggregate (§3.9.1/10) because it has a private field (§8.5.1/1). However its construction does not invoke its non-constexpr constructor because it is trivially-constructible. Anyway, this issue is fixed by adding a declaration constexpr Point() = default;.

§5.19 restricts what can be evaluated in a constant expression. One restriction is that only constexpr functions may be entered. §7.1.5 specifies what functions may be marked constexpr, but note that constexpr functions may contain (in a conditional statement) things that cannot be evaluated in a constant expression.

See the proposal papers, second and first drafts.

3
votes

If you add an in-class initializer to x, e.g. {}, then it compiles on Clang 3.4 in -std=c++1y mode:

class Point  {
public:
  constexpr double setX(double newX) { return x = newX; }
  constexpr double getX() const { return x; }
private:
  double x {}; // without init: "error: constexpr function never produces a constant expression [-Winvalid-constexpr]"
};

constexpr Point f()
{
    Point p;
    p.setX(1.0);
    return p;
}

int main()
{
    auto constexpr p = f();        
    static_assert(p.getX() == 1.0, "");
}

Live Example.

If you have other constructors for Point, you need to add constexpr Point() = default; in order for it to work.