How to construct an object either from a const reference or temporary via forwarding template

In C++17 you can simply write:

template <typename F>
auto makeFoo(F&& f)
{
    return Foo(std::forward<F>(f));
}

because of class template argument deduction.


In C++14 you can write:

template <typename F>
auto makeFoo(F&& f)
{
    return Foo<std::decay_t<F>>(std::forward<F>(f));
}

template <class F, class R = std::decay_t<F>>
Foo<R> makeFoo(F&& f)
{
  return Foo<R>(std::forward<F>(f));
}

that is a clean and simple way to solve your problem.

Decay is an appropriate way to convert a type into a type suitable for storing somewhere. It does bad things with array types but otherwise does pretty much the right thing; your code doesn't work with array types anyhow.


The compiler error is due to reference collapsing rules.

 X          X&          X const&       X&&
 int        int&        int const&     int&&
 int&       int&        int&           int&
 int const  int const&  int const&     int const&&
 int&&      int&        int&           int&&
 int const& int const&  int const&     int const&

these may seem strange.

The first rule is that a const reference is a reference, but a reference to const is different. You cannot qualify the "reference" part; you can only const-qualify the referred part.

When you have T=int&, when you calculate T const or const T, you just get int&.

The second part has to do with how using r and l value references together work. When you do int& && or int&& & (which you cannot do directly; instead you do T=int& then T&& or T=int&& and T&), you always get an lvalue reference -- T&. lvalue wins out over rvalue.

Then we add in the rules for how T&& types are deduced; if you pass a mutable lvalue of type C, you get T=C& in the call to makeFoo.

So you had:

template<F = C&>
Foo<C&> makeFoo( C& && f )

as your signature, aka

template<F = C&>
Foo<C&> makeFoo( C& f )

now we examine Foo<C&>. It has two ctors:

Foo( C& const& )
Foo( C& && )

for the first one, const on a reference is discarded:

Foo( C& & )
Foo( C& && )

next, a reference to a reference is a reference, and lvalue references win out over rvalue references:

Foo( C& )
Foo( C& )

and there we go, two identical signature constructors.

TL;DR -- do the thing at the start of this answer.


Issue is that typename provided to class is reference in one case:

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

becomes

template <>
Foo<C&> makeFoo(C& f)
{
    return Foo<C&>(std::forward<C&>(f));
}

You probably want some decay:

template <typename F>
Foo<std::decay_t<F>> makeFoo(F&& f)
{
    return Foo<std::decay_t<F>>(std::forward<F>(f));
}