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?
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.