Search code examples
c++c++11language-lawyerundefined-behaviorc++14

Is it well-defined to cast xvalues to lvalues for passing to functions?


Recently I've discovered that sometimes being able to turn rvalues temporarily into lvalues can be useful for me.

I've been using the following tool:

#include <type_traits>

template <typename T>
inline constexpr std::remove_reference_t<T> &lvalue(T &&r) noexcept {
    return static_cast<std::remove_reference_t<T> &>(r);
}

It's useful when you have to use functions that require lvalues as arguments, but you don't have any interest in what those particular values get changed into. For when you are interested in other output vectors that are not related to the given specific argument.

For example, this:

std::string get_my_file() {
    std::ifstream ifs("myfile.txt");
    return {std::istreambuf_iterator<char>(ifs), {}};
}

can be changed to this:

std::string get_my_file() {
    return {std::istreambuf_iterator<char>(lvalue(std::ifstream("myfile.txt"))),
            {}};
}

And this:

std::string temp1 = get_my_shader();
const char *temp2 = temp1.c_str();
glShaderSource(a, 1, &temp2, nullptr);

can be changed to this:

glShaderSource(a, 1, &lvalue(get_my_shader().c_str()), nullptr);

And allow things like this:

void foo(int *x) {
    std::cout << *x << std::endl;
}

foo(&lvalue(5));

I'd like to be sure whether I'm invoking undefined-behavior or not in any of this, because I don't see any, although there may be some casting rule that would turn it illegal (which I ignore). Regarding lifetime of temporaries, I don't see a problem since, AFAIK, rvalues live until the end of full-expression and usage of the function is restricted to that.

There's a recent change to the standard about reinterpret_cast and xvalues that seems to be on topic:

https://stackoverflow.com/a/26793404/1000282

EDIT:

Better version using reference collapsing as suggested:

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

Solution

  • As you said, you took care to not let any pointer or reference to the temporaries escape their scope.
    Using your lvalue-function (mine is called no_move) makes it easier to break that stricture inadvertently.

    Next, let's look at what xvalues are: Expiring objects, but objects nevertheless.
    This means, you can ignore they are on their funeral tour (if you pass them to a function, that function will naturally do so, unless you ask to take advantage).

    The last point you mentioned was calling with a prvalue, which is most certainly not an object.
    But even that is not a problem, as on calling the function, a temporary will be created.
    And that temporary will naturally also survive to the end of the statement.

    As an aside, using std::remove_reference_t<T>& is unneccessary for the return-type of lvalue, you can use T& directly and rely on the reference-collapsing-rules. Also, the static_cast and inline are superfluous.

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