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