sperber
sperber

Reputation: 661

A proper use of perfect forwarding in C++?

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

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Related Questions