Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr compound assignment operator in clang and gcc

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.

like image 294
MikeMB Avatar asked Apr 27 '15 15:04

MikeMB


People also ask

How do I declare constexpr?

constexpr variables A constexpr variable must be initialized at compile time. All constexpr variables are const . A variable can be declared with constexpr , when it has a literal type and is initialized. If the initialization is performed by a constructor, the constructor must be declared as constexpr .

What is a constexpr function?

A constexpr function is a function that can be invoked within a constant expression. A constexpr function must satisfy the following conditions: It is not virtual. Its return type is a literal type. Each of its parameters must be of a literal type.

Is constexpr function always inline?

2) A function defined entirely inside a class/struct/union definition, whether it's a member function or a non-member friend function, is always inline. 3) A function declared constexpr is always inline.

Can constexpr throw exception?

Even though try blocks and inline assembly are allowed in constexpr functions, throwing exceptions or executing the assembly is still disallowed in a constant expression.


1 Answers

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;
}
like image 100
Columbo Avatar answered Sep 28 '22 06:09

Columbo