Indirect perfect forwarding via function pointer? - c++

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.

Related

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 this class template parameter is treated as a forwarding reference?

According to https://en.cppreference.com/w/cpp/language/reference, forwarding references are either,
function parameter of a function template declared as rvalue reference.
auto&&.
Here at https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
Scott Meyers explained that, although vector::push_back(T&& x) takes a T&&, it is not a universal reference. It's just a plain rvalue reference.
However, my below code compiles and runs well:
#include <iostream>
template<typename T>
class MyClass
{
public:
void f(T&& x)
{
std::cout << __FUNCSIG__ << std::endl;
}
};
int main()
{
int i = 10;
MyClass<int&> myClass1;
myClass1.f(i); //void __thiscall MyClass<int &>::f(int &)
MyClass<int&&> myClass2;
myClass2.f(10); //void __thiscall MyClass<int &&>::f(int &&)
}
It sounds like T&& is treated as a forwarding reference here, because f(T&& x) accepts both lvalue and rvalue references. But f(T&& x) is a class template member; it is not a standalone function template. Is this against the forwarding reference definition that I mentioned above?
<<\ADD-->
In the case of the forwarding reference in a function template, the function template itself also needs to instantiate based on a specific type T. We can express this explicitly:
template<class T>
void f(T&& x)
{}
int main() {
int i = 10;
f<int&>(i); // argument is lvalue
f<int&&>(10); // argument is rvalue
}
f<\int&> and f<\int&&> are two different instances of the above function template. They are not pointing to the same function address behind the scenes either, similar to class template instantiation. Of course, we need class object to use its functions.
<--End ADD>
because f(T&& x) accepts both lvalue and rvalue references
It doesn't. You aren't comparing the same member function here. MyClass<int&>::f is a member function of one class that accepts lvalues only. Meanwhile MyClass<int&&>::f is a member function of another class, and it accepts only rvalues.
A class template is not a class. It's a cookie cutter from which classes are made. And despite the specializations having similarly named member functions, those are still different functions inside different classes.
Your code will not build successfuly if you try to pass MyClass<int&>::f an rvalue,
myClass1.f(10); // ERROR
Reference collapsing makes the generated f's accept references of different value categories, but once the class is instantiated that member will accept only a specific value category. It is not forwarding the value category that is deduced at the call site, like a forwarding reference would.
The thing about forwarding references is that they automatically become rvalue references or lvalue references during template argument deduction. The caller doesn't need to specify which they want.
In your example, you've explicitly specified int& and int&&, which forces f to have one type or the other. And then it takes lvalues or rvalues correspondingly, but not both like a forwarding reference can: myClass1.f(10); and myClass2.f(i); are both ill-formed.

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.

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

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

Type deduction of function template parameters

I have some questions concerning function templates.
My plan was to build a wrapper which derives from a user-defined class and
not only exports the public functions of that class but also its constructors.
So I decided I would use multiple constructor templates (which I presume work exactly
the same as function templates) with 1 to n parameters to satisfy most constructors needs.
These would than simply call the constructor and do something else afterwards, like
this:
template <class T>
class Wrapper : public T
{
public:
template <class U>
Wrapper(U &u) : T(u) { doSomething(); }
template <class U, class V>
Wrapper(U &u, V &v) : T(u,v) { doSomething(); }
...
};
My intent is to register the instance within the Wrapper-Ctor somewhere else and,
from that point on, it can receive calls to virtual functions defined in T.
I had to use the reference operator in the code above, in order to guarantee that
my Wrapper-Ctor does not have any side-effects on the parameters that were passed
(copy-construction).
To my surprise this always worked, except for temporaries, which is the reason why
I am confused about the types that are inferred by the compiler in this situation.
To simplify the situation I tried to do something similiar via a template function:
template <class T>
void foo(T &t)
{
int x = ""; // intentional error
}
Calling the function like this:
std::string a;
std::string &b = a;
foo(b);
To my surprise the compiler denotes [T = std::string] in its error message.
I would have expected for this to be [T = std::string&], which would have caused
passing a reference-to-reference, which is invalid.
So, why does the compiler deduce a value-type in this situation?
Is it even possible to create a Wrapper-Ctor that does what I want, does not
have any side-effects on the parameters and also accepts temporaries?
Thanks alot!
It looks like the C++ spec explicitly states that this is the intended behavior. Specifically, if you have a template function that takes in a parameter P that depends on a template type argument, if P is a reference, then the underlying type of the reference, rather than the reference type, is used to determine what type should be used for P (see §14.8.2.1/2). Moreover, this same section says that const and volatile qualifiers are ignored during this step, so the constness can be inferred automatically.
It is not possible in C++03 to provide such a thing without manually overloading for every combination of const and non-const parameters.
No expression ever has reference type. Therefor, when argument deduction deduces against the argument expression type, it cannot make a distinction between a and b because the arguments a and b both have the same type.
Refer to [expr]p5 in the spec
If an expression initially has the type "reference to T" (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis.
Somewhat late, but since I don't think this was answered completely...
For template parameter deduction, see the previous answers.
For your problem with temporaries, make the parameters const references (as in Wrapper(const U&)).
The thing is, temporaries are rvalues. The standard states that non-const references can only be bound to lvalues. Therefore, a standards compliant compiler won't let you pass temporaries(rvalues) as arguments to non-const reference parameters. (This doesn't have anything to do with templates in particular, it's a general rule).
This is to the best of my knowledge, so take it with a bit of scepticism.