5

I have the code

void prints_one()
{ cout << "one" << endl; }

int main(int argc, char *argv[])
{
    std::function<void()> foo;
    foo = prints_one;
    foo();
    return 0;
}

It works as expected; it prints "one". What I don't know is which assignment operator prototype is being invoked in the assignment and how. Looking at cpp reference, it looks like it probably is this function

template <class Fn> function& operator= (Fn&& fn);

But if that is the prototype being called, I don't understand how a function can bind to a rvalue reference. Thanks!

Update: Thanks all, I'll read up on universal references. In regards to 40two's answer; this code prints that it is an rvalue reference:

template<class Fn>
class Foo {
public:
    Foo() {}
    Foo& operator=(Fn&& x)
    {
      std::cout << std::boolalpha << std::is_rvalue_reference<decltype(x)>::value << std::endl;
    }
};

void prints_one()
{
  cout << "one" << endl;
}

int main(int argc, char *argv[])
{
  Foo<void()> bar;
  bar = prints_one;
}

This prints true

Mihir
  • 53
  • 4
  • 1
    It's a so-called "universal reference", not an rvalue reference. Reference collapsing rules apply. – chris Jun 09 '14 at 21:12
  • 2
    Scott Meyers calls it _Universal References_, see for example [Universal References in C++11](https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers). – nosid Jun 09 '14 at 21:13
  • @nosid, The only thing that doesn't call it a universal reference these days is the standard :p Of course Scott did coin the term. – chris Jun 09 '14 at 21:14
  • 1
    It's not quite a duplicate, I think, but [this question](http://stackoverflow.com/questions/20364297/why-universal-references-have-the-same-syntax-as-rvalue-references) is relevant. –  Jun 09 '14 at 21:43
  • Oh! I was unaware of universal references, thanks all! – Mihir Jun 09 '14 at 21:46

2 Answers2

8

The C++ standard is meant to be confusing! Otherwise, people like me couldn't play smart just because they were watching while the C++ standard got developed. To this end it was decided that the && notation applied to types shall mean two entirely different although also confusingly related things:

  1. When applied in a non-deduced context, i.e., with a known type T, the notation T&& means that an rvalue reference is being declared.
  2. When applied in a deduced context, e.g., in auto&& x = ... or in function template template <typename T> void f(T&& x) the notation is to mean: determine the type and "referenceness" of the argument. That the deduced type T be of different kinds depending on what was passed as argument:
    1. If a non-const lvalue of type X is being passed, the type T will be deduced as X&, and T&& becomes X& as a reference and an rvalue reference collapse into a reference.
    2. If a const lvalue of type X is being passed, the type T will be deduced as X const&, and T&& becomes X const&, due to the same reference collapsing.
    3. If an rvalue is being passed, the type T will be deduced as X, and T&& becomes X&& as there is nothing to collapse.

The motivations driving the rules on argument deduction is perfect forwarding: the argument should be forwarded to the next layer ideally look like the same kind of type as the argument which got deduced in the first places.

With this in mind, the assignment operator of std::function<...> being called is indeed

template <typename F>
std::function<...>::function(F&& f)

where the argument of the function it just deduced accordingly. In your concrete example the function pointer being passed actually is an rvalue created on the fly by decaying the function type: functions aren't really objects and can't be passed along in C++. To access them other than calling a pointer to the function is being formed. Thus, the argument is an rvalue of type void(*)().

Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
2

Quoting stuff from here:

  • T&& isn't always an rvalue reference.

  • && in a type declaration sometimes could mean an rvalue reference, but sometimes it means either rvalue reference or lvalue reference.

I'll try to contribute to the above with an example. Consider the following piece of code:

#include <iostream>
#include <type_traits>

using namespace std;

template<class F>
void foo(F&& f)
{
  std::cout << std::boolalpha << std::is_rvalue_reference<decltype(f)>::value << std::endl;
}

void prints_one()
{
  cout << "one" << endl;
}

int main(int argc, char *argv[])
{
  foo(prints_one);
  foo([](){ cout << "lambda" << endl; });
  return 0;
}
  • If you run it the output is:

false

true

  • This mean that although foo takes a T&& as an input parameter, because of the fact that prints_one is an lvalue, input parameter f will be initialized with an lvalue, and consequently it will become an lvalue reference.

  • On the other hand in the second call of foo we pass a lambda which is really a rvalue reference. As such, the input parameter f is initialized with an rvalue and consequently it becomes an rvalue reference.

  • Thus, whether input parameter is deduced to an lvalue reference or an rvalue reference depends on input parameter that is passed in foo upon the time it is called.

Update:

Regarding the updated example:

  • You are defining a template class Foo with an assignment operator Foo<T>::operator=(T&&).

  • In this case T&& is not a universal reference because there's no type deduction.

  • It is rather an rvalue reference, hence you are getting true.

  • This is due to the fact that T is already deducted by the class Foo<T>. Consequently, there can't be type deduction for input parameters of member overloaded operator Foo<T>::operator=(T&&).

101010
  • 41,839
  • 11
  • 94
  • 168
  • And how can a person tell when a function takes an rvalue reference as opposed to a universal reference? (Hint: type deduction/templates)( – Mooing Duck Jun 09 '14 at 21:48
  • 1
    As @MooingDuck says the thing to emphasize here is that for it to be a *universal reference*, the template parameter must be of the form `T&&` and `T` *must be* deduced. – Praetorian Jun 09 '14 at 22:08
  • @Praetorian it seems that someone else already answered that, so editing now would seem more like copying. – 101010 Jun 09 '14 at 22:15
  • @40two if you read my update passing the function as an assignment prints that it is an rvalue reference. – Mihir Jun 09 '14 at 22:28
  • @Mihir that's exactly what the other guys here wanted me to explain. Assignment operator here is member of class `Foo`. As such in Foo::operator=(T&&) `T&&` isn't a universal reference. – 101010 Jun 09 '14 at 22:38