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"?
struct foo : bar { template <typename... Ts> foo (Ts&&... ts) : bar (std::forward<Ts> (ts)...) {} };
). Getters and setters are rare actually, so intellisense is not a big deal here. – Valinefoo(foo const&)
) for making copies (flamingdangerzone.com/cxx11/2012/06/05/is_related.html)? It doesn't forward initializer lists properly either. On the other hand, the language now has inherited ctors (compiler writers, could you pretty please with sugar on top implement these?), so you can get something much more robust than that ctor that with a simpleusing bar::bar;
. – Niggera
field, but will still logically implementset_a
by otherwise storing whatever attributes it needs from the specified instance ofA
. Or perhaps in future the value of the field will not be orthogonal to all other data members, soset_a
might update something else too. I know, YAGNI, but if the class was calledURL
then I wouldn't necessarily want to commit to a publicprotocol
data member of typestring
even though I am willing to commit to always having aset_protocol
member function. – Wetterhorna_changed()
event in the future for example? Or debugging the change of the property... – Enwreatheunique_ptr
) and types which are copiable but not movable (ok, can't think of a concrete example here, but you can apply= delete
to a move constructor and make the object unmovable and yet copiable). – Rollickconst &
reference can bind to an rvalue. So every Copyable type is also Movable. It may not be efficiently movable, though. – Wetterhorn= delete
, having a copy constructor which acceptsconst&
will not let you assign an rvalue, because overload resolution would select the deleted move constructor. That effectively makes the object copiable but not movable. – Rollick=delete
prevents the less-good match from being selected, oops. – WetterhornCopyConstructible
and/orCopyAssignable
and "Movable" to meanMoveConstructible
and/orMoveAssignable
. But the reason I gave in my comment above was motivation at best and completely spurious at worst -- it has nothing to do with logical use or reference binding, it's an explicit requirement of the concepts. – Wetterhorn