Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which function prototype is invoked (and how) when assigning a function to a std::function type?

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

like image 692
Mihir Avatar asked Jun 09 '14 21:06

Mihir


People also ask

What is the type of std :: function?

std::function is a type erasure object. That means it erases the details of how some operations happen, and provides a uniform run time interface to them.

What is the function of STD in C++?

C++ Library - <functional> Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.

Why did we need to use an std :: function object?

It lets you store function pointers, lambdas, or classes with operator() . It will do conversion of compatible types (so std::function<double(double)> will take int(int) callable things) but that is secondary to its primary purpose.

Does std :: function allocate?

Many std::function implementations will avoid allocations and use space inside the function class itself rather than allocating if the callback it wraps is "small enough" and has trivial copying.


2 Answers

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(*)().

like image 91
Dietmar Kühl Avatar answered Sep 19 '22 01:09

Dietmar Kühl


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

like image 32
101010 Avatar answered Sep 20 '22 01:09

101010