Universal references (i.e. "forward references", the c++
standard name) and perfect forwarding in c++11
, c++14
, and beyond have many important advantages; see here, and here.
In Scott Meyers' article referenced above (link), it is stated as a rule of thumb that:
If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.
Example 1
Indeed, using clang++ we see that the following code snippet will successfully compile with -std=c++14
:
#include <utility>
template <typename T>
decltype(auto) f(T && t)
{
return std::forward<T>(t);
}
int x1 = 1;
int const x2 = 1;
int& x3 = x1;
int const& x4 = x2;
// all calls to `f` result in a successful
// binding of T&& to the required types
auto r1 = f (x1); // various lvalues okay, as expected
auto r2 = f (x2); // ...
auto r3 = f (x3);
auto r4 = f (x4);
auto r5 = f (int()); // rvalues okay, as expected
Given any description of universal references (forward references) and type deduction (see, for instance, this explanation) it is clear why the above works. Although, from the same explanation, it is not abundantly clear why the below fails to work as well.
(failed) Example 2
This question addresses the same issue. The provided answers do not, however, explain why templated types are not categorized as being "deduced".
What I am about to show (seemingly) satisfies the requirement stated above by Meyers. However, the following code snipped fails to compile, producing the error (among others for each call to f
):
test.cpp:23:11: error: no matching function for call to 'f'
auto r1 = f (x1);
test.cpp:5:16: note: candidate function [with T = foo, A = int] not viable: no known conversion from 'struct foo< int >' to 'foo< int > &&' for 1st argument
decltype(auto) f (T< A > && t)
#include <utility>
//
// It **seems** that the templated type T<A> should
// behave the same as an bare type T with respect to
// universal references, but this is not the case.
//
template <template <typename> typename T, typename A>
decltype(auto) f (T<A> && t)
{
return std::forward<T<A>> (t);
}
template <typename A>
struct foo
{
A bar;
};
struct foo<int> x1 { .bar = 1 };
struct foo<int> const x2 { .bar = 1 };
struct foo<int> & x3 = x1;
struct foo<int> const& x4 = x2;
// all calls to `f` **fail** to compile due
// to **unsuccessful** binding of T&& to the required types
auto r1 = f (x1);
auto r2 = f (x2);
auto r3 = f (x3);
auto r4 = f (x4);
auto r5 = f (foo<int> {1}); // only rvalue works
In context, since the type T<A>
of f
's parameter is deduced, surely the parameter declaration T<A>&& t
would behave as a universal reference (forward reference).
Example 3 (for clarity in describing the problem at hand)
Let me stress the following: the failure of the code in Example 2
to compile is not due to the fact that struct foo<>
is a templated type. The failure seems to be cause only by the declaration of f
's parameter as a templated type.
Consider the following revision to the previous code, which now does compile:
#include <utility>
//
// If we re-declare `f` as before, where `T` is no longer a
// templated type parameter, our code works once more.
//
template <typename T>
decltype(auto) f (T && t)
{
return std::forward<T> (t);
}
//
// Notice, `struct foo<>` is **still** a templated type.
//
template <typename A>
struct foo
{
A bar;
};
struct foo<int> x1 { .bar = 1 };
struct foo<int> const x2 { .bar = 1 };
struct foo<int> & x3 = x1;
struct foo<int> const& x4 = x2;
// all calls to `f` (again) result in
// a successful binding of T&& to the required types
auto r1 = f (x1);
auto r2 = f (x2);
auto r3 = f (x3);
auto r4 = f (x4);
It is astonishing to me that this simple change completely alters the behaviour of the type deduction for the template function f
's type parameter.
Questions:
Why does the second example not work as expected? Are there techniques to overcome this problem with templated types in c++11/14
? Are there well known, extant codebases (in the wild) making successful use of c++
's forward references with templated types?