Should all/most setter functions in C++11 be written as function templates accepting universal references?
C++C++11Move SemanticsPerfect ForwardingUniversal ReferenceC++ Problem Overview
Consider a class X
with N
member variables, each of some copiable and movable type, and N
corresponding setter functions.
In C++98, the definition of X
would likely look something like this:
class X
{
public:
void set_a(A const& a) { _a = a; }
void set_b(B const& b) { _b = b; }
...
private:
A _a;
B _b;
...
};
Setter functions of class X
above can bind both to lvalue and to rvalue arguments. Depending on the actual argument, this might result in the creation of a temporary and will eventually result in a copy assignment; due to this, non-copiable types are not supported by this design.
With C++11 we have move semantics, perfect forwarding, and universal references (Scott Meyers's terminology), which allow for a more efficient and generalized use of setter functions by rewriting them this way:
class X
{
public:
template<typename T>
void set_a(T&& a) { _a = std::forward<T>(a); }
template<typename T>
void set_b(T&& b) { _b = std::forward<T>(b); }
...
private:
A _a;
B _b;
...
};
Universal references can bind to const
/non-const
, volatile
/non-volatile
, and to any convertible type in general, avoiding the creation of temporaries and passing values straight to operator =
. Non-copiable, movable types are now supported. Possibly undesired bindings can be eliminated either through static_assert
or through std::enable_if
.
So my question is: as a design guideline, should all (let's say, most) setter functions in C++11 be written as function templates accepting universal references?
Apart from the more cumbersome syntax and the impossibility of using Intellisense-like helper tools when writing code in those setter functions, are there any relevant disadvantages with the hypothetical principle "write setter functions as function templates accepting universal references whenever possible"?
C++ Solutions
Solution 1 - C++
You know the classes A and B, so you know if they are movable or not and if this design is ultimately necessary. For something like std::string
, it's a waste of time changing the existing code unless you know you have a performance problem here. If you're dealing with auto_ptr
, then it's time to rip it out and use unique_ptr
.
It's usually preferred now to take arguments by value if you don't know anything more specific- such as
void set_a(A a) { _a = std::move(a); }
This permits the use of any of the constructors of A
without requiring anything except movability and offers a relatively intuitive interface.