This is just a word of caution. C++14 will not be backwards compatible with C++11 in one aspect of constexpr
functions.
In C++11, when you define a constexpr
member function, it implicitly obtains a const
qualifier:
// C++11 struct NonNegative { int i; constexpr int const& get() /*const*/ { return i; } int& get() { return i; } };
The first declaration of function get
obtains a const
qualifier even though we did not type it. Therefore, the two declarations declare two function overloads: a const
and a non-const
one.
In C++14 this will no longer be the case: the two declarations will define the same non-const
member function with two different return types: this will cause a compilation error. If you have already started playing with constexpr
functions and you rely on this implicit const
qualifier, I suggest that you start adding it explicitly, so that your code still compiles when you switch to C++14 compilers.
What’s wrong with implicit const
?
The problem starts when you try to use our type like this:
// C++11 constexpr int i = NonNegative{2}.get(); // ERROR
The (somewhat unusual) rules of C++ dictate that when picking a member function on a temporary class object, the non-const
variant is a viable candidate, and in fact preferred to the const
one. Our selected non-const
member function get
is not constexpr
, so it cannot be used to initialize a constexpr
variable and we get a compile-time error. We cannot fix it by making the non-const
overload constexpr
, because this would imply the const
qualifier…
I said that the rules of best function matching are unusual, because they are quite the opposite when we are selecting the non-member function for a temporary object. In that case we choose const
lvalue reference variant, and the to non-const
one is not even a viable match:
int const& fget(NonNegative const& n) { return n.i; } int& fget(NonNegative& n) { return n.i; } int i = fget(NonNegative{2});
In the case above we select the first overload: a reference to non-const
lvalue from the second overload cannot be bound to a temporary.
The above ‘irregularity’ is a problem related to how member functions are matched, and not directly related to constexpr
functionality. However, there is another irregularity added along constexpr
to C++11. If we define non-member constexpr
getters, no implicit const
is added:
// C++11 constexpr int const& get(NonNegative const& n) { return n.i; } constexpr int& get(NonNegative& n) { return n.i; } NonNegative N = readValue(); constexpr int * P = &get(N); // non-const overload int main() { *P = 1; }
Observe what happens. The global N
is not a constant. Therefore the second, non-const
overload is picked in line 6. But the non-const
overload is still a constexpr
function! Because rule “constexpr
implies const
” only applies to the implied this
argument of non-static member functions. A constexpr
function can take a reference to non-const
object and return a reference to its non-const
sub-object. This is all fine: we do not know the value of N
, but by only passing references around and not reading the value we are not doing anything impossible. Next, we take the address to N
’s sub-object. This is still fine: the address of a global is constant — known at compile-time. However, the value at the address P
is not constant and can be later assigned to.
If the above example looks too esoteric, consider the following, a bit more realistic one:
constexpr NonNegative* address(NonNegative& n) { return &n; } NonNegative n{0}; // non-const constexpr NonNegative* p = address(n);
This works fine. But if you try to make address
a member function, it will stop working:
// C++11 struct NonNegative { // ... constexpr NonNegative* maddress() { return this; } // ERROR }; NonNegative n{0}; // non-const constexpr NonNegative* p = n.maddress();
This is because maddress
is implicitly const
, this
has type NonNegative const*
and cannot be converted to NonNegative*
.
Note that it is not a member function that is really const
, but the implicit (this
) function argument. The declaration of the member function could be depicted in a pseudo language as:
// PSEUDO CODE struct NonNegative { // ... constexpr NonNegative* maddress(NonNegative const& (*this)); };
And this implicit function argument, unlike any other function argument, obtains a (sometimes unwanted) const
qualifier.
This asymmetry will be removed in C++14. If you want a const
qualifier for the implicit argument (this
), you need to type it yourself. The following is going to be valid in C++14:
// C++14 struct NonNegative { int i; constexpr int const& get() const { return i; } constexpr int& get() { return i; } };
Further reading
- Richard Smith, “
constexpr
member functions and implicitconst
.” - N3690 — “Programming Languages — C++,” especially § C.3.1.
Were you reading my mind http://stackoverflow.com/questions/17245050/clang-3-3-and-gcc-4-7-const-vs-constexpr
For me, these “somewhat unusual” rules are one of the biggest mistake in C++. It shouldn’t have been ever allowed that temporary objects are modifiable. If you regard how Object().method() is being called, you can quickly realize that inside this Object::method(), *this is actually a mutable lvalue, which means that because of this call it actually turns a rvalue into a modifiable lvalue (imagine: Object& ref() {return *this;} and then showme(Object().ref()) to overcome impossibility to pass temporary by reference). Of course, if temporary objects weren’t modifiable, it would be hard to use expressions like “–end()”, but I don’t think it would be really harmful. On the other hand, when there’s no rule of having temporary objects ALWAYS constant, it suppresses some compiler optimizations: the temporary object of a class type (unlike temporary object of builtin type) must always be an object and have its contents allocated in memory, even if it’s in particular case not necessary.
Similar thing is when returning a value by function: const makes a difference, but only for class types.
This mistake has also caused that the expression of using assignment to a temporary object can be only detected by the compiler for builtin types (which is a part of detection of mistakenly used = instead of ==). For class types it’s impossible because the compiler just calls a mutable method on an object, and mutable methods can be called on a temporary object.
The inconsistency of temporary objects allowed to act as lvalue is used with Proxy classes, e.g. vector v; … v[0] = true;
It may be argued that the use of vector<bool> was evil to begin with (see Howard Hinnant view on that: https://isocpp.org/blog/2012/11/on-vectorbool) but the use of Proxy class in general is relevant and usually requires this rule.
Good article!
But the text “I said that the rules of best function matching are unusual, because they are quite the opposite when we are picking the best non-member function for a temporary object. In that case we prefer const lvalue reference to non-const one” is not related to the sample below. First of all the word “prefer” strictly is incorrect related to temporary objects. We _can’t_ bind them to non-const lvalue reference. Secondly after reading this text about binding to const reference I see at the example where N is bind to non-const reference. And the third. “However, the value at the address P is not constant and can be later assigned to. ” – what’s wrong there?
All other parts of the article clearly explain why constexpr and const for members are ortogonal topics. Thanks!
Indeed. I have just read it again, and I find some pieces lacking coherency. I will try to rewrite it. Thanks for pointing it out.
I have changed the text. I hope it is clear now.
Pingback: What changes introduced in C++14 can potentially break a program written in C++11? - Tutorial Guruji
Pingback: What changes introduced in C++14 can potentially break a program written in C++11? - PhotoLens
Pingback: C++ 中复杂却很有意思的SFINAE技术 - luozhiyun`s Blog