Variadic template: lvalue vs rvalue references - c++

The following code compiles with c++11 or higher
#include <iostream>
#include <cstdlib>
#include <cstdio>
#define p 1
// p1: prototype 1
template <class Function, class... Args>
void addv(Function&& f, Args&... args) {
std::cout << f(args...) << std::endl;
}
// p2: prototype 2
template <class Function, class... Args>
void addv(Function& f, Args&... args) {
std::cout << f(args...) << std::endl;
}
int add(int& a, int& b) {
return a+b;
}
class Adder {
public:
int operator () (int& a, int&b) {
return a+b;
}
};
int main() {
int a = 2;
int b = 1;
Adder adder;
addv<int (int&,int&),int,int>(add,a,b); // uses p1 OR p2
addv<Adder,int,int>(Adder(),a,b); // uses p1
addv<Adder,int,int>(adder,a,b); // uses p2
}
If prototype 2 is removed, and this is compiled, the following error happens:
exp.cpp:36:36: error: no matching function for call to ‘addv<Adder, int, int>(Adder&, int&, int&)’
addv<Adder,int,int>(adder,a,b); // uses p2
^
exp.cpp:9:10: note: candidate: template<class Function, class ... Args> void addv(Function&&, Args& ...)
void addv(Function&& f, Args&... args) {
^~~~
exp.cpp:9:10: note: template argument deduction/substitution failed:
exp.cpp:36:36: note: cannot convert ‘adder’ (type ‘Adder’) to type ‘Adder&&’
addv<Adder,int,int>(adder,a,b); // uses p2
^
Why adder cannot be converted from a lvalue to a rvalue implicitly as is needed to have the line addv<Adder,int,int>(adder,a,b); use prototype 1?
Is it possible to explicitly create a rvalue reference of adder to have it correctly match prototype 1?

It can't be converted because the idea with using rvalue and lvalue references in function signatures is exactly to make sure you get one or the other. Not either one.
Normally that is used because if you get an rvalue, you can move it. If you get an lvalue you need to copy it. You can also make sure that a function is only callable with an rvalue, or lvalue.
When passing functions the usual way is to take the parameter by value. That also works with both rvalues and lvalues. This is how it's (almost always?) done in the standard library. Function pointers and Functors are generally very cheap to copy.
If you want a signature that can take both rvalues and lvalues you can use const &.
Also note that since Function&& is a template parameter, it is a forwarding reference. Thats means it will become an rvalue reference or lvalue reference depending on what you pass in.
When you call the function with an explicitly specified parameter though
addv<Adder,int,int>(adder,a,b);
^--this
the template parameter will be deduced to exactly Adder, and your function will then accept only rvalues, since the signature says Function&& -> Adder&&.
The easy way to make the code work is to not explicitly specify the template parameter.
addv(adder,a,b);
Then you can remove prototype 2 and all the function calls will work.
If you really want to or need to specify the parameters, you can use std::move to convert an lvalue to an rvalue at the calling site.
addv<Adder,int,int>(std::move(adder),a,b);
Edit: Convert might be a bit misleading. It's actually a cast. Nothing is changed except the value category.

Your code will compile if you don't explicitly specify template parameters and let template type deduction work it out:
addv(Adder(),a,b); // this is deduced to
// addv<Adder, int, int>(Adder&&, int&, int&)
addv(adder,a,b); // this is deduced to
// addv<Adder&, int, int>(Adder& &&, int&, int&),
// which becomes addv<Adder&, int,int>(Adder&, int&, int&)
// after reference collapsing
So you do want to explicitly specify template parameter, it needs to be
addv<Adder&, int,int>(adder,a,b);
Have a look at Scott Meyers' excellent article on universal reference (and in particular 'reference collapsing'), hope it helps.

Related

std::forward through an example

I would like to go over an example using std::forward because sometimes I can make it work and some other times I can’t.
This is the code
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
template <class T>
void wrapper(T&& int1, T&& int2){
f(std::forward<T>(int1), std::forward<T>(int2));
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
wrapper(int1, int2);
}
I am passing int 1 and int 2. These are lvalues. They are silently converted to &int1, &int2. These are converted using &&. But reference collapsing keeps them just &int1, &int2.
f takes && parameters
If I pass simply int1 and int2 as they are I am passing &int1, &int2. This does not work.
So I pass std::forward(int1) std::forward(int2).It should be the same as using static_cast<T&&>. Because of this, thanks to referencing collapsing I can pass to every function f (theoretically even one that accepts only l-value references).
My code does not compile and my logical reasoning has probably some contradictions.
candidate function not viable: no known conversion from 'int' to 'int &&' for 1st argument
void f(int&& int1, int&& int2){
How on earth did I get a simple int after using all these ampersands?
Additional question: My compiler asks me to use wrapper<int &> instead of only wrapper(some parameters). Can I just leave it like in my code, or I need to manually put wrapper<int &> (this is what my compiler is asking me to add). Why do I need <int &> int this case?
The whole problem stems from the forwarding references using same symbols as rvalue ones, but not being the same.
Take the following code:
template<typename T>
void f(T&& t)
{
//whatever
}
In this case T&& is a forwarding reference. It is neither T&& in the sense of rvalue-reference (reference to temporary), nor is it T&. Compiler deduces that at compile time. Notice though, it does not have type specified either, it's a template paremeter. Only in case of those parameters the forwarding reference semantics applies (another way of writing it down is a auto&& in lambdas, but the deduction works the same way).
Thus when you call int x= 3; f(x); you're effectively calling f(int&). Calling f(3) calls effectively f(int&&) though.
void g(int&& arg)
arg is and rvalue reference to int. Because the type is specified, it's not a template argument! It's always an rvalue reference.
Thus, for your case
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
template <class T>
void wrapper(T&& int1, T&& int2){
f(std::forward<T>(int1), std::forward<T>(int2));
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
wrapper(int1, int2); //call wrapper(int&, int&);
//Then f(std::forward<T>(int1), std::forward<T>(int2));-> f(int&, int&), because they are lvalues! Thus, not found, compilation error!
}
Live demo: https://godbolt.org/z/xjTnjcqj8
forward is used to convert the parameter back to the "valueness" it had when passed to the function. We can see how this works using this example
void foo(int&) { std::cout << "foo(int&)\n"; }
void foo(int&&) { std::cout << "foo(int&&)\n"; }
template <typename T> void wrapper(T&& var) { foo(std::forward<T>(var)); }
int main()
{
int bar = 42;
wrapper(bar);
wrapper(42);
}
which outputs
foo(int&)
foo(int&&)
So, when you pass wrapper an lvalue, forward will forward that along, and the lvalue accepting overload of foo is called. When you pass an rvalue to wrapper, forward will convert var back into an rvalue1 and the rvalue overload of foo is called.
Since your f function only accepts rvalues, that means your wrapper function will also only work for rvalues. You are basically just trying to do f(int1, int2) in main, and that wont work.
The reason you get the error message no known conversion from 'int' to 'int &&' is that it is trying to tell you that there is no conversion from an lvalue int into a reference to an rvalue int.
1: This is needed because as a named variable, it is an lvalue, even if it is a reference to an rvalue.
I'll start off with something mentioned in the comments, and that is that having only one template parameter here relates the two parameters and interferes with the usual deduction process. Normally when forwarding, you deduce a type for each forwarded parameter independently. In this case, it will still work, but only because the call site passes two things with the same type and the same value category (lvalue, rvalue, etc.).
Here's what a typical wrapper would look like, truly forwarding the arguments independently:
template <class T, class T2>
void wrapper(T&& int1, T2&& int2){
f(std::forward<T>(int1), std::forward<T2>(int2));
}
With that out of the way, I'll move on to the intuitive reason your code doesn't compile. If forwarding is done correctly, the wrapper function's existence won't change anything. By the above reasoning, your wrapper function meets this criteria for this particular call. Let's see what happens when it's gone:
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
f(int1, int2);
}
You might be able to spot the error more clearly now. f takes rvalues, but it's being given lvalues. The wrapper preserves the value category of its arguments, so they're still lvalues when handed to f. To fix this with the wrapper, it's the same as without—pass rvalues:
int main(){
f(20, 30);
// Alternatively: f(std::move(int1), std::move(int2));
}
Now, reviewing the points made:
I am passing int 1 and int 2. These are lvalues. They are silently converted to &int1, &int2. These are converted using &&. But reference collapsing keeps them just &int1, &int2.
Don't think about the variables themselves here, but the types. Because you pass in two lvalue ints, T is deduced to be int&. Following that, the actual parameter types, T&&, are then int& as well because of reference collapsing. Thus, you have a function stamped out with two int& parameters. In the forward calls, T is int&, so its reference-collapsed return type is again int&. Thus, the call expressions have the type int and are lvalues (specifically because the return type is int&—the language calls that out as an explicit rule).
As a side note, &int1 isn't clear to me because its C++ meaning is taking the address of int1, an entirely irrelevant meaning here. I think what you're trying to say is that it's an lvalue or that a parameter's type is an lvalue reference.
If I pass simply int1 and int2 as they are I am passing &int1, &int2. This does not work.
This is true for the the reasons discussed earlier. The parameters are themselves lvalues, so a function taking rvalues won't accept them.
So I pass std::forward(int1) std::forward(int2).It should be the same as using static_cast<T&&>. Because of this, thanks to referencing collapsing I can pass to every function f (theoretically even one that accepts only l-value references).
Yes, this is the point of forwarding. It lets you preserve the value category of the arguments given to the wrapper, so the same template can be used to pass along arguments to a function regardless of which value categories it accepts. It's up to the caller of the wrapper to provide the correct value category in the first place and then the wrapper simply promises not to mess with it.
How on earth did I get a simple int after using all these ampersands?
This is just how the type is displayed in the error message. Although the parameter is an int&, the expression has the type int. Remember that an expression has both a type and a value category and that they're separate properties. For example, you can have an rvalue reference parameter int&& x and the expression x will still be an int and an lvalue.

Is it possible to omit template parameter in `std::forward`?

The standard signature of std::forward is:
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&) noexcept;
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
Because the parameter type isn't T directly, we should specify the template argument when using std::forward:
template<typename... Args>
void foo(Args&&... args)
{
bar(std::forward<Args>(args)...);
}
However sometimes the template argument is not as simple as Args. auto&& is a case:
auto&& vec = foo();
bar(std::forward<decltype(vec)>(vec));
You can also imagine more complicated template argument types for std::forward. Anyway, intuitively std::forward should know what T is but it actually don't.
So my idea is to omit <Args> and <decltype(vec)> no matter how simple they are. Here is my implementation:
#include <type_traits>
template<typename T>
std::add_rvalue_reference_t<std::enable_if_t<!std::is_lvalue_reference<T>::value, T>>
my_forward(T&& obj)
{
return std::move(obj);
}
template<typename T>
T& my_forward(T& obj)
{
return obj;
}
int main()
{
my_forward(1); // my_forward(int&&)
int i = 2;
my_forward(i); // my_forward(int&)
const int j = 3;
my_forward(j); // my_forward(const int&)
}
When obj is rvalue reference, for example int&&, the first overload is selected because T is int, whose is_lvalue_reference is false;
When obj is lvalue reference, for example const int&, the second overload is selected because T is const int& and the first is SFINAE-ed out.
If my implementation is feasible, why is std::forward still requiring <T>? (So mine must be infeasible.)
If not, what's wrong? And still the question, is it possible to omit template parameter in std::forward?
The problematic case is when you pass something of rvalue reference type but which does not belong to an rvalue value category:
int && ir{::std::move(i)};
my_forward(ir); // my_forward(int&)
Passing type to std::forward will ensure that arguments of rvalue reference types will be moved further as rvalues.
The answer by user7860670 gives you an example for the case where this breaks down. Here is the reason why the explicit template parameter is always needed there.
By looking at the value of the forwarding reference you can no longer reliably determine through overload resolution whether it is safe to move from. When passing an lvalue reference parameter as an argument to a nested function call it will be treated as an lvalue. In particular, it will not bind as an rvalue argument, that would require an explicit std::move again. This curious asymmetry is what breaks implicit forwarding.
The only way to decide whether the argument should be moved onwards is by inspecting its original type. But the called function cannot do so implicitly, which is why we must pass the deduced type explicitly as a template parameter. Only by inspecting that type directly can we determine whether we do or do not want to move for that argument.

Function template deduction l-value reference and universal reference

Let's say I have the function copy:
template <typename Buf>
void copy(
Buf&& input_buffer,
Buf& output_buffer)
{}
In which input_buffer is a universal reference and output_buffer is an l-value reference.
Reference collapsing rules make sure input_buffer is indeed, regardless of the deduced type of Buf, an universal reference and output_buffer is indeed an l-value reference.
However, I wonder how type Buf is deduced here.
I found out that copy is passed an r-value as input_buffer, (and an l-value as output_buffer, obviously) Buf is a non-reference type.
If I were to pass two l-values however, the program does not compile:
int i = 4;
int j = 6;
_copy(i, j);
I would expect the compiler to deduce Buf to int&. Following the reference collapsing rules, I would expect input_buffer to become an l-value reference, that is, & + && -> &, and output_buffer to become an l-value reference too; & + & -> &.
So the question is: Why doesn't this code compile?
(Note: I am not necessarily asking for a solution to the problem, but for an explanation.)
If I need to elaborate, feel free to ask.
EDIT:
if call: copy(i, j);
GNU GCC Compiler gives:
error: no matching function for call to 'copy(int&, int&)'
note: candidate: template void copy(Buf&&, buf&)
note: template argument deduction/substitution failed:
note: deduced conflicting types for parameter 'Buf' ('int&' and 'int')
if call:
copy<int&>(i, j);
OK.
a) Type deduction for forwarding reference:
template<class T>
void f(T&& val) {}
[a.1] when you pass Lvalue T is deduced to be T&. So you have
void f(T& && ){} -after reference collapsing-> void f(T&){}
[a.2] when you pass Rvalue T is deduced to be T. So you have
void f(T&& ) {}
b) Type deduction for reference except forwarding reference:
template<class T>
void f(T& param){}
when you pass Lvalue, T is deduced to be T. param has type T& but template argument is T, not T&.
So below code compiles
int i = 10;
copy(20,i);
because type deduction for first argument returns Buf==int since you passed 20 Rvalue.
And result of deduction for second argument also returns Buf==int. So in both
cases Buf is the same, code compiles.
Code which doesn't compile:
int i=1;
int j=2;
copy(i,j);
What is deduced type for first argument? You are passing L-value, so Buf is int&.
Second deduction returns Buf==int. These two deduced types are not the same, that is why
code doesn't compile.

Compile error when trying to use std::result_of

I want to deduce the return type of a function coming as a template parameter. Consider the following code:
#include <type_traits>
struct Result {};
Result foo() { return Result{}; }
template<typename Factory>
void check(Factory) {
using ActualResult = typename std::result_of<Factory()>::type;
static_assert(std::is_same<Result, ActualResult>::value, "");
}
int main() {
check(foo);
}
This works as expected. However, if I change the parameter of check() to const Factory&, then it does not compile. The error from gcc is:
prog.cc: In instantiation of 'void check(const Factory&) [with Factory = Result()]':
prog.cc:14:14: required from here
prog.cc:9:66: error: function returning a function
using ActualResult = typename std::result_of<Factory()>::type;
^
prog.cc:10:65: error: function returning a function
static_assert(std::is_same<Result, ActualResult>::value, "");
^
What's the problem here? How can I make it work?
Functions (just like arrays) can neither be passed as prvalue arguments or returned as prvalues.
Therefore, template <typename Factory> void check(Factory), which takes a prvalue argument, will cause foo to decay to the function pointer, and check(foo) will cause Factory to be deduced as Result (*)(). Finally, result_of<Factory()> gives the result of calling the callable type that is the function pointer with no arguments.
When you change check to check(const Factory&), the function takes an lvalue, and so there is no decay, and Factory is deduced as the function type Result(). This is not a type that you are allowed to pass to result_of*, which requires either a callable type or a reference to a function. That is, you should use result_of<Factory&()> in that case.
*) In C++11. The rules for result_of may have been relaxed in later revisions, and C++17 deprecates result_of.

Deducing knowledge of original types, whilst simultaneously forwarding

Summary: I want to end up with a function that deduces the exact types it was called with and takes (e.g.) a tuple that forwards them (the types of which will be different from the exact types the function was called with).
I'm stuck trying to "know" via deduction the types of the arguments to a given function, whilst simultaneously forwarding them. I think I might be missing something crucial about how this works.
#include <tuple>
#include <string>
#include <functional>
template <typename ...Args>
struct unresolved_linker_to_print_the_type {
unresolved_linker_to_print_the_type();
};
void f(int,double,void*,std::string&,const char*) {
}
template <typename F, typename ...Args>
void g1(F func, Args&&... args) {
unresolved_linker_to_print_the_type<Args...>();
auto tuple = std::forward_as_tuple(args...);
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
template <typename F, typename T, typename ...Args>
void g2(F func, const T& tuple, Args... args) {
unresolved_linker_to_print_the_type<Args...>();
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
int main() {
int i;
double d;
void *ptr;
std::string str;
std::string& sref = str;
const char *cstr = "HI";
g1(f, i,d,ptr,sref,cstr);
g2(f, std::forward_as_tuple(i,d,ptr,sref,cstr), i,d,ptr,sref,cstr);
}
What I'd like to see is a scenario where when my function (e.g. g1 or g2) gets called it knows and can use both the original types - int,double,void*,std::string&,const char* and the forwarded arugments too.
In this instance I don't seem to be able to find this information from within g1 or g2. The (deliberate, to print out the types) linker error shows me in g1 they are:
int&, double&, void*&, std::string&, char const*&
int&, double&, void*&, std::string&, char const*&
and in g2:
int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&
There are two thing I don't get here:
Why do none of the printed (via the linker error) types match what I actually passed in? (int,double,void*,std::string&,const char). Can I deduce what I actually was passed? Preferably with "natural" syntax, i.e. everything just once and nothing explicitly written out. I can explicitly write:
g2<decltype(&f),decltype(std::forward_as_tuple(i,d,ptr,sref,cstr)),int,double,void*,std::string&,const char*>(f,std::forward_as_tuple(i,d,ptr,sref,cstr),i,d,ptr,sref,cstr);
but that's "unwieldy" to say the least!
In g1 the presence of && in the function signature declaration seems to alter the types in the template parameter Args itself. Compare that with:
template <typename T>
void test(T t);
Or:
template <typename T>
void test(T& t);
using either of those with:
int i;
test(i);
doesn't change the type of T. Why does the && change the type of T itself when & doesn't?
Answer to first question:
Arguments to functions are expressions, not types. The difference between these two is expressed in chapter 5 [expr], p5:
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.
Thus, there is no difference what-so-ever between g(str) and g(sref). g() always sees a std::string, and never a reference.
Additionally expressions can be lvalue or rvalue (actually that's a simplification of the C++11 rules, but it is close enough for this discussion - if you want the details they're in 3.10 [basic.lval]).
Answer to second question:
Template parameters of the form:
template <class T>
void g(T&&);
are special. They are unlike T, T&, or even const T&& in the following way:
When T&& binds to an lvalue, T is deduced as an lvalue reference type, otherwise T deduces exactly as per the normal deduction rules.
Examples:
int i = 0;
g(i); // calls g<int&>(i)
g(0); // calls g<int>(0)
This behavior is to support so called perfect forwarding which typically looks like:
struct A{};
void bar(const A&);
void bar(A&&);
template <class T>
void foo(T&& t)
{
bar(static_cast<T&&>(t)); // real code would use std::forward<T> here
}
If one calls foo(A()) (an rvalue A), T deduces per normal rules as A. Inside of foo we cast t to an A&& (an rvalue) and call bar. The overload of bar that takes an rvalue A is then chosen. I.e. if we call foo with an rvalue, then foo calls bar with an rvalue.
But if we call foo(a) (an lvalue A), then T deduces as A&. Now the cast looks like:
static_cast<A& &&>(t);
which under the reference collapsing rules simplifies to:
static_cast<A&>(t);
I.e. the lvalue t is cast to an lvalue (a no-op cast), and thus the bar overload taking an lvalue is called. I.e. if we call foo with an lvalue, then foo calls bar with an lvalue. And that's where the term perfect forwarding comes from.
types (even in C++) are mostly a compile type notion (except of course the RTTI in vtables).
If you need entirely dynamic types, then C++ might not be the best language for that.
You might perhaps extend GCC (actually g++, assuming it is at least 4.6) with a plugin or a GCC MELT extension (MELT is a high level domain specific language to extend GCC) which does what you want (e.g. for instance providing an additional builtin which encode the type of its arguments in some constant string, etc...), but that does require some work (and is specific to GCC).
But I don't understand why you want to do such baroque things in C. If dynamic typing is so important to you, why don't you use a dynamically typed language??