what do I mean when a data member is Type&&?

The following class has 2 possible implementations that compile correctly. The
first uses a Func&& data member and forward like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename Func>
struct Logger2
{
	Logger2(Func&& func, const string& name)
		: name(name), func{std::forward<Func>(func)}
	{}

	Func&& func;
	string name;

	void operator()() const
	{
		cout << "Entering " << name << endl;
		func();
		cout << "Exiting " << name << endl;
	}
};


the other has no forward and the reference to the function is lvalue reference (Func&) like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename Func>
struct Logger2
{
	Logger2(Func&& func, const string& name)
		: name(name), func{func}
	{}

	Func& func;
	string name;

	void operator()() const
	{
		cout << "Entering " << name << endl;
		func();
		cout << "Exiting " << name << endl;
	}
};


which is correct? My belief is that since we are using an rvalue reference to a template argument in the constructor we HAVE to forward such reference and the reference must be &&.

is this true? How does a Func&& field differ from a Func& field?


Regards,
Juan

If you're getting an rvalue reference then why not simply std::move() the value into a non-reference Func member?
no Logger2 ctor receives both lvalue and rvalue references.... thus the forward (I think)

I am a bit lost here.

What am I saying when I declare a data member as rvalue reference (Func&&)?
both defintions work but which is the best? Func&& in ctor ==> std::forward in ctor ==> Func&&
as a data member correct?
no Logger2 ctor receives both lvalue and rvalue references.
Uh... No. The constructor takes an rvalue reference. lvalue references aren't implicitly convertible to rvalue references. Example:
1
2
3
4
5
void f(std::string &&){}

std::string s = "foo";
f(s); // Error
f(std::move(s)); //OK 
I am calling Logger2 like so:

1
2
Logger2{ f, "f() function" }();		
Logger2{ []() {cout << "Hi" << endl; }, "Whew" }();



f in the first ctor call is an lvalue reference to a function f defined elsewhere, the first parameter to the ctor in the second call is an rvalue (the temporary lambda)....

Thus Logger2 needs a Func&& as first parameter of its ctor and either:
1- func without forward and Func& as data member of the class OR
2- func with forward and Func&& as data member of the class

I get this ! But why??

How does Func&& differ from Func& ?

but the parameter to the ctor refers to a template parameter Func so Func&& accepts BOTH lvalue and rvalue references ... I believe it they call it universal reference when used this way with templates..

Check this code:

1
2
3
4
5
6
7
8
9
template<typename T>
void g(T&& ) {}

void useG()
{
	std::string s = "foo";
	g(s); // NO ERROR - OK
	g(std::move(s)); //OK 
}


Last edited on
Scott Meyers invented the term "universal reference" during C++11's development, but the committee decided that the term "forwarding reference" was a more descriptive choice and made it the official name. Anyway, deduced class template arguments are not forwarding references. Therefore in your first snippets, Func&& is always an rvalue reference type, it ought to be moved from.

It is possible to initialize an rvalue reference from an lvalue expression with function type. Expressions that name functions, like f, are always lvalue expressions, but this is a special case probably added to ease generic programming:
http://eel.is/c++draft/dcl.init.ref#5.3

Both definitions work but which is the best

The former is certainly better, but both are non-ideal.

If Logger2 contains an rvalue reference to Func, then declarations like
Logger2 logger([]{ std::cout << "func"; }, "whatever");
Immediately create a dangling reference.

It's better to work with lvalue references, so that the above would result in a compile-time error, although I second helios and suggest storing func by value:

mbozzi wrote:
Storing a reference is contrary to existing practice, has negative performance implications, and makes the interface more challenging to use properly. If value semantics were supported, the user could always pass std::ref(func) if reference semantics were desired.
http://cplusplus.com/forum/beginner/269786/

Reference members also make code harder to implement. Sticking with values should result in fairly straightforward code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <utility>

template<typename Fn> class logger
{
  Fn f_;

public:
  explicit logger(Fn f): f_(std::move(f)) {}
  auto operator()() const { return f_(); }
};

int main()
{
  logger l([]{ std::cout << "hello!\n"; });   
  l();
}
Last edited on
Topic archived. No new replies allowed.