Is this a universal reference? Does std::forward make sense here? - c++

Consider this snippet of code, which uses the common idiom of having a function template construct an instance of a class template specialized on a deduced type, as seen with std::make_unique and std::make_tuple, for example:
template <typename T>
struct foo
{
std::decay_t<T> v_;
foo(T&& v) : v_(std::forward<T>(v)) {}
};
template <typename U>
foo<U> make_foo(U&& v)
{
return { std::forward<U>(v) };
}
In the context of Scott Meyers' "universal references", the argument to
make_foo is a universal reference because its type is U&& where U is
deduced. The argument to the constructor of foo is not a universal reference
because although its type is T&&, T is (in general) not deduced.
But in the case in which the constructor of foo is called by make_foo, it
seems to me that it might make sense to think of the argument to the constructor
of foo as being a universal reference, because T has been deduced by the
function template make_foo. The same reference collapsing rules will apply
so that the type of v is the same in both functions. In this case, both T
and U can be said to have been deduced.
So my question is twofold:
Does it make sense to think of the argument to the constructor of foo as being
a universal reference in the limited cases in which T has been deduced
within a universal reference context by the caller, as in my example?
In my example, are both uses of std::forward sensible?

make_foo is in the same ballpark as "right", but foo isn't. The foo constructor currently only accepts a non-deduced T &&, and forwarding there is probably not what you mean (but see #nosid's comment). All in all, foo should take a type parameter, have a templated constructor, and the maker function should do the decaying:
template <typename T>
struct foo
{
T v_;
template <typename U>
foo(U && u) : v_(std::forward<U>(u)) { }
};
template <typename U>
foo<typename std::decay<U>::type> make_foo(U && u)
{
return foo<typename std::decay<U>::type>(std::forward<U>(u));
}
In C++14 the maker function becomes a bit simpler to write:
template <typename U>
auto make_foo(U && u)
{ return foo<std::decay_t<U>>(std::forward<U>(u)); }
As your code is written now, int a; make_foo(a); would create an object of type foo<int &>. This would internally store an int, but its constructor would only accept an int & argument. By contrast, make_foo(std::move(a)) would create a foo<int>.
So the way you wrote it, the class template argument determines the signature of the constructor. (The std::forward<T>(v) still makes sense in a perverted kind of way (thanks to #nodis for pointing this out), but this is definitely not "forwarding".)
That is very unusual. Typically, the class template should determine the relevant wrapped type, and the constructor should accept anything that can be used to create the wrapped type, i.e. the constructor should be a function template.

There isn't a formal definition of "universal reference", but I would define it as:
A universal reference is a parameter of a function template with type [template-parameter] &&, with the intent that the template parameter can be deduced from the function argument, and the argument will be passed either by lvalue reference or by rvalue reference as appropriate.
So by that definition, no, the T&& v parameter in foo's constructor is not a universal reference.
However, the entire point of the phrase "universal reference" is to provide a model or pattern for us humans to think about while designing, reading, and understanding code. And it is reasonable and helpful to say that "When make_foo calls the constructor of foo<U>, the template parameter T has been deduced from the argument to make_foo in a way that allows the constructor parameter T&& v to be either an lvalue reference or an rvalue reference as appropriate." This is close enough to the same concept that I would be fine moving on to the claim: "When make_foo calls the constructor of foo<U>, the constructor parameter T&& v is essentially a universal reference."
Yes, both uses of std::forward will do what you intend here, allowing member v_ to move from the make_foo argument if possible or copy otherwise. But having make_foo(my_str) return a foo<std::string&>, not a foo<std::string>, that contains a copy of my_str is quite surprising....

Related

Indirect perfect forwarding via function pointer?

Lets consider ordinary perfect forwarding:
class Test
{
public:
Test() = default;
Test(Test const&) { std::cout << "copy\n"; }
Test(Test&&) { std::cout << "move\n"; }
};
void test(Test)
{ }
template <typename T>
void f(T&& t)
{
test(std::forward<T>(t));
}
int main()
{
std::cout << "expect: copy\n";
Test t;
f(t);
std::cout << "expect: move\n";
f(Test());
return 0;
}
So far everything is fine. But if we now introduce a function pointer I get the problem that I seem not to be able to declare universal (forwarding) references:
decltype(&f<Test>) ptr = &f<Test>;
// above produces ordinary r-value references
// making below fail already on compilation:
ptr(t);
Template function pointers get problematic as well:
template <typename T>
void(*ptr)(T&& t) = &f<T>; // again resolved to r-value reference
At first, they resolve to pure r-value reference as well, additionally they define a bunch of pointers instead of a single one, making the approach unusable within class scope:
class C
{
template <typename T>
void(*ptr)(T&& t); // fails (of course...)
};
So question now is: Is it possible at all to have indirect perfect forwarding via function pointers?
Admitted, already fearing the answer is 'no' (and currently falling back to l-value references), but still in hope of having overlooked something somewhere...
A forwarding reference is not only a hypothetical concept. It is the name given to a specific kind of rvalue reference which has special deduction rules in template argument deduction.
Specifically according to [temp.deduct.call]/3:
A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.
Except for this special rule (and one other in [temp.deduct.type]), a forwarding reference behaves just like any other rvalue reference.
In particular when providing a template argument for T, there is no template argument deduction happening and substitution is performed directly. In substitution the usual reference collapsing rules are applied.
So f<Test> will yield the function parameter Test&& which is a rvalue reference and f<Test&> will yield the function parameter Test& (collapsed from && being applied to Test&).
These are also the template arguments that would be deduced for a rvalue argument and a lvalue argument in a function call without explicit template arguments. If the reference was not a forwarding reference, then T could never be deduced to a reference and the function parameter would always be a rvalue reference after substitution. The special adjustment of A mentioned in the quote allows T to be deduced to an lvalue reference type, so that the collapsing rules will result in an lvalue reference function parameter as well.
Forwarding references can only exist in templates. A function or specialization of a template cannot have a forwarding reference. They are not somehow a different category of reference from rvalue/lvalue references. They work in templates only by deducing to different types for different value categories of arguments and produce distinct specializations for each value category.
So, since a function pointer must point to a function, not a function template, which of the two specializations for the value category of the argument to choose, has to be decided when taking the function pointer.
If deduction of any kind is expected, then a function pointer cannot offer that. Instead a lambda or functor type should be used which can perform deduction and can choose the function or function template specialization to call based on value category.
The first example uses template argument deduction and reference collapsing rules to compute the correct type. When you explicitly supply the template arguments this disables deduction and the supplied type is substituted directly, resulting in the rvalue reference.
Perfect forwarding requires the template argument to be deduced. A function pointer can only point to a single function, not to a set of overloads, and not to a function template.
You can get a function pointer to either the r-value reference instantiation:
decltype(&f<Test&&>) ptr1 = &f<Test&&>;
ptr1(Test()); // output: move
or the l-value instantiation:
decltype(&f<Test&>) ptr2 = &f<Test&>;
ptr2(t); // output: copy
But not to both at the same time. ptr1 and ptr2 are pointers to functions of different type.
When you want something to hold not just a single function but more than one overload you can use a type with member functions. For example with a lambda expression:
auto fw = [](auto&& t){ test(std::forward<decltype(t)>(t)); };
fw(t); // output: copy
fw(Test()); // output: move
Background of the question is a mis-reading of Scott Meyer's article about forwarding references (called 'universal references' there).
The article gave the impression of such forwarding references existing as a separate type in parallel to ordinary l-value and r-value references. This is not the case, though, instead it is a hypothetical construct to explain special deduction rules that exist if template parameters serve as function parameter types.
As such a type does not exist in consequence it is not possible to declare a function pointer using that type, proving indirect perfect forwarding via function pointer impossible to realise.

Why does a template argument of type T& resolves to T?

Here for example, b is of type int& but f(b) resolves to f(int):
#include <type_traits>
template <typename T>
void f(T arg) {
static_assert(std::is_reference<T>::value); // fails
}
void g() {
int a = 5;
int& b = a;
f(b);
}
I know the standard dictates it - I'm asking, why? What goes wrong (or - becomes 'surprising') if the reference isn't dropped?
There are two things at play here.
What if you, as the author of f, wants to write a template function where the user is required to copy/move a parameter into the function?
In non-template code, that prototype would look like this: void f(SomeType st);. Any caller of f has no choice but to copy/move into the parameter. Whether they have an object, a reference to an object, or something else. st shall be an object separate and distinct from the rest.
Template argument deduction is intended to look like the non-template version where possible. So if you have template<typename T> void f(T t);, and you call it with f(some_object), that should work exactly like the untemplated version.
The other thing at play here is that reference variables are supposed to just be a different name for some object. That is, a and b, should, all things being equal, behave identically. So f(a) ought to do the same thing as f(b).
The rules of template argument deduction favor both of these. It keeps references behaving exactly like the object, and it allows the template function to behave like an equivalently-defined non-template version.
If you want a reference type, you must forgo template argument deduction and specify it directly: f<decltype((b))>(b). Note the use of double-parentheses here; that's important for getting decltype to be a reference.
According to the rules of the language, f(b) is not calling f with an expression of type int&. It is calling f with an lvalue of type int. The same would also occur if you did f(a).
In general, when a reference variable is mentioned by name, it is an expression that is an lvalue of the referenced type. The fact that the variable itself is a reference is not visible to the type system.
Thus, f never deduces T to have reference type. From f's point of view, the argument is never actually a reference.
What goes wrong (or - becomes 'surprising') if the reference isn't dropped?
Consider following function:
template <typename T>
void f(T arg) {
something = std::move(arg);
// ...
f(lvalue); // copies
f(str::move(lvalue)); // moves
The intention is to copy from an lvalue, and move from an rvalue.
If T deduced to a reference, then lvalue argument would be moved from, which is in this case undesirable.
I don't want to copy the argument.
If you're writing the function, then specify a reference parameter using & symbol, and not an object parameter. That way the argument can never be copied into the call (you can still copy inside the function).
If you're calling a function that accepts an object parameter, then pass an rvalue and not an lvalue. That way you will copy only if the move is a copy, and there is no copy elision involved.
Doesn't make sense to me that the template type deduction rules decide for me that I do
You decided to use T. You should instead decide to use T&, T&& or const T& if you want a reference.

Why does std::is_rvalue_reference not do what it is advertised to do?

For example if I have
#include <type_traits>
struct OwnershipReceiver
{
template <typename T,
class = typename std::enable_if
<
!std::is_lvalue_reference<T>::value
>::type
>
void receive_ownership(T&& t)
{
// taking file descriptor of t, and clear t
}
};
copied from How to make template rvalue reference parameter ONLY bind to rvalue reference?
the poster uses !std::is_lvalue_reference instead of the immediately more obvious std::is_rvalue_reference. I've verified this in my own code where the former works and the later doesn't.
Can anybody explain why the obvious doesn't work?
Because for forwarding reference, T will never be deduced as an rvalue reference. Suppose passing an object of type int to OwnershipReceiver, if the object is an lvalue, T will be deduced as an lvalue-reference, i.e. int&; if the object is an rvalue, T will be deduced as an non-reference, i.e. int. That's why std::is_rvalue_reference<T>::value won't work because it's always false.
Note that the purpose of the code is to make sure the parameter type of OwnershipReceiver is an rvalue-reference, it doesn't mean the type of T is an rvalue-reference too.
In other words, the point here it to distinguish lvalue-reference and non-reference, so !std::is_reference<T>::value works too.
BTW: If you stick to std::is_rvalue_reference, you can use std::is_rvalue_reference<T&&>::value as you found in the comment, or use it on the parameter t, e.g.
template <typename T>
auto receive_ownership(T&& t) -> typename std::enable_if<std::is_rvalue_reference<decltype(t)>::value>::type
{
// taking file descriptor of t, and clear t
}

Why deduced types of fun(T t) and fun(T && t) are different in C++?

template <typename T>
void fun1(T t) {}
template <typename T>
void fun2(T && t) {}
int i = 1;
fun1(i); // the deduced type of T is int
fun2(i); // the deduced type of T is int &
The deduced type of T in fun1(i) and fun2(i) are int and int & respectively, can anyone explain the mechanism how compiler do deduction?
UPDATE
This question is not a duplicate of Type not deduced to be r-value reference: why not?, because:
The later question explained the deduction rules for :
template <class T>
void foo(T&& )
Here, I want to know the difference of deduction rules for
template <class T>
void foo(T&& )
and
template <class T>
void foo(T )
That is aperfect forward which can produce different outcomes depending on what arguments are provided to the template.
Perfect forwarding reduces the need for overloaded functions and helps
avoid the forwarding problem. The forwarding problem can occur when
you write a generic function that takes references as its parameters
and it passes (or forwards) these parameters to another function. For
example, if the generic function takes a parameter of type const T&,
then the called function cannot modify the value of that parameter. If
the generic function takes a parameter of type T&, then the function
cannot be called by using an rvalue (such as a temporary object or
integer literal).
Ordinarily, to solve this problem, you must provide overloaded
versions of the generic function that take both T& and const T& for
each of its parameters. As a result, the number of overloaded
functions increases exponentially with the number of parameters.
Rvalue references enable you to write one version of a function that
accepts arbitrary arguments and forwards them to another function as
if the other function had been called directly.
Rvalue Reference
Forwarding references
Forwarding references are a special kind of references that preserve the value category of a function argument, making it possible to forward it by means of std::forward. Forwarding references are either:
function parameter of a function template declared as rvalue reference to cv-unqualified type template parameter of that same function template:
auto&& except when deduced from a brace-enclosed initializer list.
Forwarding Reference
How it is donde is explained here:
Template Deduction
Because the type of i is int, not int&. As [temp.deduct.call] paragraph 1 says
Template argument deduction is done by comparing each function template
parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below.
And [temp.deduct.call] paragraph 4 says
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above).
The forwarding reference case is a special case. As [temp.deduct.call] paragraph 3 explicitly says
If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Templates and using rvalues references as a parameter

So I have a std::map< std::string, boost::any > called OptionsMap and I want to create a function that takes any type (therefore the template) and stores it in the map.
Would the next code work?
template <typename T>
void Set(std::string optionName, T&& optionValue)
{
OptionsMap[optionName] = optionValue;
}
Typically, when a function template accepts a universal reference (i.e. an rvalue reference of a deduced type), you should cast the function parameter with forward so as to obtain a value of the same value category as was provided by the argument in the function call:
template <typename T>
void Set(std::string optionName, T&& optionValue)
{
OptionsMap[optionName] = std::forward<T>(optionValue);
// ^^^^^^^^^^^^^^^
}
The parameter variable optionValue itself is always an lvalue, so without the cast, you would be making copies of what should be moves. If the type is copyable, this will be less efficient than expected; if the type isn't copyable (e.g. unique_ptr), it's worse: your function now accepts an argument that it will subsequently fail to compile with.
Of course. Why wouldn’t it work?
boost::any::operator= accepts an argument of any data type satisfying ValueType.
I would just take by value and move it, for forward-compatibility when boost::any starts supporting move semantics.
template <typename T>
void Set(std::string optionName, T optionValue)
{
OptionsMap[optionName] = std::move(optionValue);
}
Passing an rvalue to this function will move it when it’s movable. That’s guaranteed by the standard.