c++parameter-passingxvaluevalue-categoriesprvalue

How to distinguish between pr-values and x-values


In the following code,

#include <utility>

struct literal_type
{
    // ...
};

class my_type
{
public:
    my_type(literal_type const& literal);    // (1)
    my_type(literal_type     && literal);    // (2)

    // ...
};

void foo()
{
    literal_type literal_var { /* ... */ };

    my_type var1 (literal_var);       // Calls (1)
    my_type var2 (std::move(var));    // Calls (2)
    my_type var3 (literal_type{});    // Calls (2)
}

I understand that the value category of the argument passed in the constructor of var1 is an l-value, var2 is an x-value and var3 is a pr-value.

I would like that the constructor of my_type accepts var3, while var1 and var2 should emit a compiler error. var1 is easily solved by removing constructor (1), but I cannot find the way to distinguish between var2 and var3.

Is there any way to distinguish between x-value references and pr-value references?


Solution

  • You can't. You can distinguish xvalue and prvalues at the call site, but once you've passed them to a function that accepts them, you've lost the information of which of the two it was.

    The reason is that the value category is a property of an expression, and inside of the constructors literal has always the lvalue value category, because it's the name of a parameter. The value category of the corresponding argument, instead, can be encoded in the type of the parameter if you declare it as forwarding reference, but that will carry you as far to distinguish lvalue vs rvalue, not giving you the lvalue vs xvalue vs prvalue granularity.

    #include <iostream>
    #include <memory>
    #include <utility>
    
    struct literal_type
    {
    };
    
    class my_type
    {
    public:
        my_type(literal_type const&) {}
        template<typename T>
        my_type(T&&) {
            static_assert(std::is_same_v<T, literal_type>);     // passes
            static_assert(std::is_same_v<T&&, literal_type&&>); // passes
        }
    
    };
    
    void foo()
    {
        literal_type literal_var { };
    
        my_type var2 (std::move(literal_var));    // Calls (2)
        my_type var3 (literal_type{});    // Calls (2)
    }
    

    And if the original value category of the argument is not encoded in the type of the parameter, where would you ever get it from?

    After all, the purpose of std::move is exactly to dress up a value like a temporary (whether it was already or not).

    In other words, if you use f(std::move(nonTemporary)) and f(temporary) at the call site, why would you ever want nonTemporary to be treated differently from temporary? If you really want, then you're misusing std::move in the first place.


    I saw the suggestion of Make the parameter type non-copyable and non-movable, and pass by value, but I would never take such a route before clarifying first what you actually want to do.