Juraj Blaho
Juraj Blaho

Reputation: 13471

Rvalue reference overloading when base type is function reference

I have a class similar to this that is supposed to take a function object and use it later:

template<typename F>
class A {
public:
  A(const F& f) : _f(f) {}
  A(F&& f) : _f(std::move(f)) {}

private:
  F _f;
};

I also define a convenience initialization function and a dummy test function:

template<typename F>
A<F> MakeA(F&& f) {
  return A<F>(std::forward<F>(f));
}

void foo() {}

I get an error that it is not possible to overload A<F>::A(F&&) with A<F>::A(const F&) for F = void(&)() when I call:

auto a = MakeA(foo);

I understand that I can fix it by having function pointer instead of function reference and also lambdas work nicely:

auto a1 = MakeA(&foo);
auto a2 = MakeA([]{});

What is special about function references and why overloading does not work there?

Test code here.

Upvotes: 3

Views: 181

Answers (2)

ecatmur
ecatmur

Reputation: 157424

The error message from clang is more illuminating:

6 : error: multiple overloads of 'A' instantiate to the same signature 'void (void (&&)())'

This is because for any lvalue reference type T = U&, T&& and T const& are the same type, per the reference-collapsing rules in [dcl.ref]:

6 - If [...] a type TR [is] a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR [...]

using T = int&;
static_assert(std::is_same<T&&, T const&>::value, "!!");

The real problem is that you're applying the "universal reference / std::forward" pattern outside its region of applicability; it is appropriate only for perfect forwarding of arguments. Your code will also break if you pass a lambda lvalue:

auto l = []{};
auto d = MakeA(l);    // breaks

The correct way to write a type-deducing construction function is to apply decay to the type of the argument (cf. make_optional):

template<typename F>
A<typename std::decay<F>::type> MakeA(F&& f) {
  return A<typename std::decay<F>::type>(std::forward<F>(f));
}

Upvotes: 4

Jarod42
Jarod42

Reputation: 217990

With F = void(&)(), F&& and const F& are the same type thus the error.

Upvotes: 1

Related Questions