Is this data member Func&& a universal reference?

Hi,

Is the data member Func&& func a universal reference (according to Scott Meyers) or an rvalue reference which if initialized with an rvalue will create a dangling reference to a temporary object?

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;
	}
};



In this use, we first pass an lvalue and then an rvalue. When passing an rvalue and capturing it as a universal reference inside Logger2, does the reference there constitute a dangling reference?

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



Regards
Last edited on
Is the data member Func&& func a universal reference

No:
http://eel.is/c++draft/temp.deduct.call#3.sentence-3
But if I pass an lvalue to the class' ctor, func is stored as an lvalue reference
while if I pass an rvalue to the ctor, func is stored as an rvalue reference
it would seem then, that the data member func in class Logger2 has exact type depending on what was passed to in its ctor
My debugger shows this to be the case...
Last edited on
No, because rvalue references can be initialized, as a special case, from lvalue expressions with function type. I cited the standard about this in your prior thread.

This special case is obscuring the fact that func is always an rvalue reference.

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

void f() {};
int x;

int main()
{
    static_assert(std::is_same_v<decltype((f)), void(&)()>,
      "(f) is an lvalue expression with function type");
    
    // both rvalue and lvalue references to void() can be initialized from a 
    // lvalue expression of type void()
    void(&&rrf)() = f;
    void(&rf)() = f;
    
    // The same is not true for non-function types
    int&& rrx = x; // error: binding rvalue reference to lvalue
    int&  rx  = x;
}


The distinction between rvalue and lvalue doesn't have meaning for expressions with function type. I presume the special case was added to ease generic programming.

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

template<typename T> struct A
{
  A(T&&) {};
  
  // Unless T is explicitly specified:
  static_assert(std::is_rvalue_reference_v<T&&>, 
    "T&& is always rvalue reference");
};

int main()
{
  A a(2); // choosing some non-function type T
  int x;
  A b(x); // error: cannot bind rvalue reference to lvalue (of non-function type)
}


IMO, this is so complicated it's basically unmanageable.
Last edited on
My debugger shows this to be the case...

I wonder if the debugger is interpreting the rvalue reference to function as an lvalue reference?

Maybe someone else wants to chime in?
Last edited on
ok mbozzi I got it - I am dealing with a special case!!

However there are only 2 more questions and I will rest:

1- an rvalue reference always binds to a temporary object or to an unnamed object? it extends its life or only if it is moved? can you expand a bit on how rvalue references differ from lvalue references please? I have been reading so much I am now confused...
2- in the ctor, is Func&& always an rvalue reference or is it a forwarding reference (universal as per Meyer)?


Thanks!!!
I am getting there!


Last edited on
can you expand a bit on how rvalue references differ from lvalue references please?


The distinction is straightforward, ignoring esoteric stuff like we just discussed:
- lvalue references bind to lvalue expressions;
- rvalue references bind to rvalue expressions.
But this isn't helpful without an understanding of lvalue and rvalue expressions.

Every expression has a value category and a type. Some expressions are categorized as rvalue expressions, while others are categorized as lvalue expressions.

Note that value category is a property of expressions (e.g., f(2)+3 or x), and not a property of objects nor even of values. There is no relationship between "rvalues" (i.e., rvalue expressions) and unnamed objects or temporary objects.

As a rough approximation, an expression is an lvalue if you can take its address. For instance, we can use this rule to determine that the expression 1.2+3.4 is an rvalue, because &(1.2+3.4) does not compile. Similarly we can determine that the expression std::cout is an lvalue because &std::cout compiles just fine. However, this is only an approximation. If there is any doubt you'll need to look at this fairly intimidating reference page (don't read it now):
https://en.cppreference.com/w/cpp/language/value_category

If an rvalue expression (2) is used to initialize an rvalue reference
int&& rrx = 2;

Then an object of type int is (created, or "materialized", then) used to initialize rrx. That object's lifetime is extended to the lifetime of the reference. The lifetime is only extended if the object is temporary.
Since rrx is mutable, we can modify the referenced object through rrx:
rrx = 4;

Because of a special case, lvalue references to const can bind to rvalues too, with similar behavior:
int const& crx = 2;
This old rule allows us to pass rvalues as function arguments:
1
2
3
4
std::string g() { return "foo"; }
void f(std::string const& s);

f(g()); // ok, despite that g() is an rvalue expression 

Although clearly the referenced object can't be modified through crx - it's const:
crx = 4; // error
However, lvalue references to non-const cannot (usually) be initialized with rvalue expressions. This is demonstrated above.

The expression rrx is an lvalue. If we take its address we get the address of the object bound to the reference. This is exactly the same situation as taking the address of crx. The point is that rvalue references are not automatically rvalues. References are aliases for objects, and objects do not have value category. This is fundamental, so make sure you understand why it's true.

In the ctor, is Func&& always an rvalue reference or is it a forwarding reference

It's an rvalue reference.

Forwarding references appear in two cases:
1.) auto&& x = expression;
2.) template <typename T> void f(T&& x) {}; f(expression);
Class template argument deduction, e.g.:
3.) template <typename T> struct A { A(T&& x) {} } a(expression);
Does not produce forwarding references - so Func&& is always an rvalue reference.




Last edited on
Beutiful! Great explanantion. Thanks a lot. Still processing it...

Thanks and God bless you for the time it took you to answer!

Thanks mbozzi
Topic archived. No new replies allowed.