Reputation: 661
I wonder if the following is a proper use of perfect forwarding in C++ (explanation below):
class A
{
public:
//...
template <typename T> A( T&& b,const C& c ) : _b{ std::forward<T>( b ) },_c{ c } { }
//...
private:
B _b;
C _c;
};
An object of type A
contains a B
and a C
object. It is required that an A
object can be constructed from a pair (B b
,C c
) alone. Now the context dictates that it will always be the case that c
has to be copied into _c
for such a construction. Therefore, c
is passed as const C&
. On the other hand, depending on the case, b
has to be copied into _b
, or it may be possible to move b
it into _b
. Will the above code accomplish this correctly, i.e. will it generate appropriate constructors depending on the way objects of type A
are constructed in the code? I.e., is this a proper use of perfect forwarding?
I think it does the right thing, but the reason why I'm irritated: I think this is somewhat ugly as I only want to call the constructor with lvalues or rvalues of type B
in the first argument. The above code would - in principle - allow any type T
. Is the alternative of defining the two methods
A( B& b,const C& c ) : _b{ b },_c{ c } { }
A( B&& b,const C& c ) : _b{ std::move( b ) },_c{ c } { }
to be preferred?
Upvotes: 2
Views: 145
Reputation: 275405
Anything that B
can be constructed from as one argument will work in your sample code.
And that isn't a bad thing.
If B
has a constructor that takes an int
, you can pass an int
as the first parameter. Instead of creating a temporary B
then moving it into your structure, it will instead directly construct the B
in your structure from the int
.
If you really, really want what you are describing, you can enforce it with three means:
// copy:
A( B const& b,const C& c ) : _b{ b },_c{ c } { }
// move:
A( B&& b,const C& c ) : _b{ std::move( b ) },_c{ c } { }
or
template <typename T, typename=std::enable_if<
std::is_same<
typename std::decay<T>::type,
B
>::value
>::type> A( T&& b,const C& c ) : _b{ std::forward<T>( b ) },_c{ c } { }
which blocks any kind of conversion. Finally, if B
is cheap to move, you can just do:
A( B b, const C& c ): _b(std::move(b)), _c(std::move(c)) {}
which follows the idiom "if you want speed, pass by value".
Here implicit conversion from the argument to B
is blocked as well, unless the caller explicitly converts.
Upvotes: 3