7
votes

I have the following code:

main.cpp

#include <cstdint>
#include <type_traits>

enum class FooEnum : uint8_t{
    Foo1 = 0, 
    Foo2 = 1
};

constexpr uint32_t& operator|= (uint32_t& lhs, FooEnum rhs) {
    return lhs |= 1u << static_cast<uint8_t>(rhs);
}

int main() {
    uint32_t bar{0};
    bar|=FooEnum::Foo1;
}

So essentially, the |= operator is supposed to take an enum and set the bit, whose position corresponds to its integral value.

When compiled with clang++ 3.5.0 on fedora 21, everything works fine, but when compiled with g++ 4.9.2, it throws an error saying that this is not a constant-expression:

main.cpp: In function ‘constexpr uint32_t& operator|=(uint32_t&, FooEnum)’:
main.cpp:16:2: error: expression ‘(lhs = (lhs | (1u << ((int)rhs))))’ is not a constant-expression
  }
  ^

This is true for all kinds of compiler flag combinations, but you can e.g. test it with g++ -std=c++11 -o a.out main.cpp (c++14 doesn't make a difference)

So my questions are:

  1. Which of the compilers is right (and why)?
  2. Is there a way to implement the operator|= such that g++ will accept it as a constexpr?

EDIT:
In case you are wondering, why I tried to declare the operator as constexpr in the first place although this isn't required in the example:
In my actual code, I'm using the |=-operator in order to implement the (constexpr) |-operator, which I wanted to be usable in a constexpr expression, but before that, I stumbled over the difference between the two compilers, not realizing, that gcc4.9 doesn't fully support c++14 (yet accepting the -std=c++14 flag).
When using the operator to actually initialize a global constexpr variable, even clang only compiles it with c++14 flag.

1
Isn't operator|= inherently non-const?Barry
@Barry: As a member function yes, but this is a free function. Also constexpr and the constness of a member function are two different concepts.MikeMB
Assignment is not allowed in a C++11 constant expression. (Assignment and compound-assignment are explicitly mentioned as forbidden inside a constant expression in C++11[expr.const]/2)dyp
@dyp: Thank you, I didn't know that, but its the same for -std=c++14.MikeMB
"Relaxing the requirements on constexpr functions" is only supported since gcc 5. The paper linked includes allowing assignment inside constant expressions.dyp

1 Answers

8
votes

The expression lhs |= 1u << static_cast<uint8_t>(rhs) can never be a constant expression itself, because it modifies lhs. The rule that forbids this in C++14 is §5.19/2.15 (an effectively equivalent one exists in C++11 as well):

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;

In C++11, it was required to be one, due to §7.1.5/5:

For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required.

There exists no argument that makes the returned expression a constant expression after invocation substitution: The assignment prevents that. Hence the program is ill-formed in C++11 (but no diagnostic is required), and when compiling with -std=c++11, GCC shows compliant behavior.
In C++14 that rule was adjusted:

For a non-template, non-defaulted constexpr function […], if no argument values exist such that an invocation of the function […] could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

This enables the return expression itself to be a non-constant expression, as long as the function is evaluate-able inside another core constant expression, e.g. from within another constexpr function:

constexpr auto foo(FooEnum rhs)
{
    uint32_t x = 0;
    x |= rhs;
    return x;
}

foo(FooEnum::Foo1) is a core constant expression, hence operator|= can be invoked in a core constant expression, hence the function definition is well-formed.

As noted by @dyp in the comments, GCC only supports the "relaxing constraints on constexpr functions"-feature since version 5. GCC 5.1 compiles your code.

So now the bodies of constexpr functions usually consist of statements that aren't constant expressions themselves. An example following the first, quoted section shows a function incr, which GCC would reject as well:

constexpr int incr(int &n) {
    return ++n;
}

constexpr int h(int k) {
    int x = incr(k); // OK: incr(k) is not required to be a core
                     // constant expression
    return x;
}