9

Is the following code legal (by C++11 and/or C++14 standard(s))?

#include <iostream>
#include <utility>

using namespace std;

void foo(int &a) {
    cout << a << endl;
}

int main() {
    foo(reinterpret_cast<int &>(move(5)));
}
  • If yes, is it undefined behavior?
  • If it's not undefined behavior, can I even mutate a inside foo without it becoming UB?

It compiles on clang 3.5, not on gcc 4.9. GCC error:

➤ g++-4.9 -std=c++1y sample.cpp -o sample                                                                                        
sample.cpp: In function 'int main()':
sample.cpp:11:40: error: invalid cast of an rvalue expression of type 'std::remove_reference<int>::type {aka int}' to type 'int&'
     foo(reinterpret_cast<int &>(move(5)));
                                        ^

EDIT

FYI, a custom made cast that's less hairy than the previous, and which works on C++11 for both GCC and Clang, would be the following lvalue function:

#include <iostream>

namespace non_std {
    template <typename T>
    constexpr T &lvalue(T &&r) noexcept { return r; }
}

void divs(int &a, int &b) {
    int t = a;
    a /= b;
    b /= t;
}

int main() {
    using namespace std;
    using namespace non_std;

    int i_care_for_this_one = 4;
    divs(i_care_for_this_one, lvalue(2));
    cout << i_care_for_this_one << endl;
}
oblitum
  • 11,380
  • 6
  • 54
  • 120
  • why don't you try to compile it using standard compliant compiler & see what happens next? – Raptor Nov 07 '14 at 02:33
  • @Raptor by standard compliant compiler I mean an ideal one that would implement it without errors, open to undefined compilation behavior only when the standard itself is open to interpretation. – oblitum Nov 07 '14 at 02:36
  • 1
    You might mention the error message you receive on GCC. Visual Studio 2013 says `error C2102: '&' requires l-value`. – Retired Ninja Nov 07 '14 at 02:42
  • @RetiredNinja updated. msvc error is somewhat alien to me =D – oblitum Nov 07 '14 at 02:46
  • 1
    @MattMcNabb The argument is an xvalue, therefore a glvalue, and the last paragraph says that a glvalue may be converted to a reference. – Brian Bi Nov 07 '14 at 02:46
  • @Brian thanks for looking into that! Please provide an answer (if you're sure ;-)). – oblitum Nov 07 '14 at 02:48
  • 2
    @Brian: "The argument is an xvalue," - not true if you mean the `5`... it's a *prvalue* per 3.10/1 "The value of a literal such as `12`, `7.3e5`, or `true` is also a prvalue." (Note that prvalues are not glvalues). – Tony Delroy Nov 07 '14 at 02:56
  • 1
    @TonyD It becomes an xvalue after being `move`d. – Brian Bi Nov 07 '14 at 02:57
  • Just as a "thought experiment" - for whatever it's worth - `std::move()` is a library function getting a prvalue argument - you'd normally expect an `int` value to be passed in a CPU register. And at the other end, you'd normally expect an `int&` to be *implemented* like an `int*`, so we're looking at a need for `move` to somehow have allocated a memory address to copy the value into. I find it quite remarkable that clang apparently does so... suggests `move()` is using some compiler built-in that effectively requests an extra `int` entry on the stack... vaguely like (a fixed) `alloca`? – Tony Delroy Nov 07 '14 at 05:42
  • @TonyD that's what I was impressed from start, because it was compiling and working, even though I had no idea of standard backing this or not. – oblitum Nov 07 '14 at 06:23
  • @pepper_chico: yeah - can understand why you'd be concerned. Considering *"for lvalues, a reference cast `reinterpret_cast(x)` has the same effect as the conversion `*reinterpret_cast(&x)` with the built-in `&` and `*` operators"*, combined with [cppreference's](http://en.cppreference.com/w/cpp/language/value_category) for rvalues including prvalues *"&std::move(val) are invalid"*, but I can't find any Standard quotes backing cppreference's assertion. I think Dietmar Kühl's answer's the closest to covering this.... – Tony Delroy Nov 07 '14 at 06:30
  • @TonyD Sad but that's on note-section and it explicitly narrows the explanatory material to lvalues. Also, Michael Wong's addition that got into the standard should have a purpose I guess, and I think the question's code is some example of application of the added relaxing rules: if it can't do this, what could this subtle change allow? – oblitum Nov 07 '14 at 06:47
  • @TonyD The diff at http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1268 seems to back my reasoning. – oblitum Nov 07 '14 at 06:55
  • @TonyD, Also, just recalled, literals being accepted in `const &T` parameters are not a new thing and were never a problem, so this's just a slight extension and Brian quoted well [dcl.init.ref] in his answer. – oblitum Nov 07 '14 at 07:07
  • Because the literal is placed on the stack in the parameter space. so a reference can be taken on it, but const casting it and changing it wont change the literal. I heard you can have crashes (signals) trying to change stuff in the constant memory on some OS. `move` does not take a prvalue, it takes a universal reference. I'm not sure this should be absolutely legal, but I love your question. upvoted it. – v.oddou Nov 21 '14 at 03:34

3 Answers3

7

Update: The code is ill-formed in C++11. Answer below is for C++14. See note at the end of this answer.

I believe this code is both well-formed and well-defined. Here's why.

The result of std::move is an xvalue [1], which is a type of glvalue; and converting a glvalue to an lvalue reference with reinterpret_cast appears to be allowed by the wording of the standard:

A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). — end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.73

Since "pointer to int" can be converted to "pointer to int", this reinterpret_cast is also allowed. The standard doesn't say anything about whether the destination type has to be an lvalue reference or rvalue reference.

The result of the cast is well-defined by the paragraph above: it refers to the same object as the source glvalue---that is, a temporary int object with the value 5. ([dcl.init.ref] specifies that a temporary is created when a prvalue is bound to a reference.)

Accessing the value through the int& also doesn't violate any aliasing rules since the original object was also of type int. In fact I believe it would even be well-defined to modify the temporary through the lvalue thus obtained.

Note: The C++11 wording says "lvalue expression", not "glvalue expression". The wording with "glvalue expression" is from N3936, which is the final working draft for C++14. I'm not an expert in how the standardization process works, but I believe this means that the change of "lvalue" to "glvalue" was already voted in by the committee, and when ISO publishes the C++14 standard, it's going to be pretty similar to what it says above.

[1] Except in the rare case in which the argument is a function; in that case the result is an lvalue, since there are no function rvalues.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • I guess your answer is the right one until there's some counter-argument to all of them. – oblitum Nov 07 '14 at 03:00
  • 1
    The wording in @hs_ answer seems the one in the C++11 standard (I can't check veracity). The wording you mention [is on the github draft](https://github.com/cplusplus/draft/blob/master/source/expressions.tex#L2281), which I'm not sure is same as for C++14 standard. I'm confused about this, and whether the code is legal in C++14 and not on C++11. (clang still compiles on C++11 mode though) – oblitum Nov 07 '14 at 03:31
  • Thanks, I'll accept this answer. Will drop the choice only in case someone tells me C++14 final final ISO got magically changed. – oblitum Nov 07 '14 at 04:26
  • The answer lacks any mention of object lifetime. Will upvote if you mention it. – Euri Pinhollow Jan 28 '18 at 17:30
  • CWG1268 was accepted as a DR, so it backpropagates the wording change to C++11. – Language Lawyer Nov 18 '20 at 14:07
5

The issue turns on whether reinterpret_cast is allowed to convert xvalues to lvalues. Contrary to what others are pasting, the relevant paragraph (5.2.10.11) only mentions lvalues:

An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast..

There is a proposal submitted by Michael Wong to change the wording to glvalue, but it appears to have hit a wall:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1268

I take it that this means, as of now, the conversion is not legal, since it only explicitly allows the conversion from lvalues.

hs_
  • 181
  • 1
  • 3
1

The relevant section in the standard is 5.2.10 [expr.reinterpret.cast]. There are two relevant paragraphs:

First, there is paragraph 1 which ends in:

No other conversion can be performed explicitly using reinterpret_cast.

... and paragraph 11 as none of the other apply:

A glvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast. The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). —end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.

All the other clauses don't apply to objects but only to pointers, pointers to functions, etc. Since an rvalue is not a glvalue, the code is illegal.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • As Brian points out, the result of `std::move` is an xvalue (which is a glvalue); [basic.lval/1] "The result of calling a function whose return type is an rvalue reference is an xvalue.", and `std::move` one-argument version is such a function – M.M Nov 07 '14 at 02:52
  • @MattMcNabb also, `std::move` is on purpose there, since clang provides an error without it (hence the question). – oblitum Nov 07 '14 at 02:53
  • As far as I can see the code must be valid due to the `move` → *xvalue* → *glvalue*. I wonder where the `std::remove_reference` (sans reference) comes from. – Cheers and hth. - Alf Nov 07 '14 at 02:56
  • @Cheersandhth.-Alf I think gcc just removes the reference from `std::remove_reference::type&&` when it prints the error message, which makes sense since there are formally no expressions of reference type – Brian Bi Nov 07 '14 at 03:01
  • @Alf that's part of the return type of `std::move` – M.M Nov 07 '14 at 03:11
  • The diff from http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1268 [seems targetted](http://stackoverflow.com/questions/26793072/can-reinterpret-cast-or-any-cast-convert-xvalues-to-lvalues#comment42165477_26793072) to allow this. [It's on final working draft for ISO C++14](http://stackoverflow.com/a/26793255/1000282). – oblitum Nov 07 '14 at 06:58