Re: Template setters
Sorry for the late reply.
That is the logical problem that I try to bring your attention at.
Most types that lack some ability should lack it. The reason is
conceptual nature of type. Some things (like thread, file descriptor)
are not copyable. Others (like some atomic) are not movable. Numerous
things (most dynamically polymorphic objects) are neither. That is
because of the very nature of the objects and not because of strange
whim of designers.
I am not negating the nature of types being (non)copyable/movable.
My point is exactly the opposite - some things should exhibit such
(lack of) properties and we should respect it. Yet we should not tie
ourselves to knowing the properties of such types, when that
knowledge is not necessary. Using template setters frees us from the
need of such knowledge.
It is needless complication added to code if it anticipates that
something that is highly unlikely (a thread becomes copyable) may
happen. C++ is strongly typed. Interfaces dictate types, generic
interfaces dictate conceptual nature of types (with traits and
'enable_if's). "Anything goes" is not good policy for C++.
You have a point there with "anything goes" policy. But let me
address previous point first. That anticipation is tied to the
knowledge about the type - if you don't know/care about the type,
you also have no anticipations of it. The setters, in their nature,
only require the type to be "settable" - to have operator = in more
technical terms. Template setters only make this requirement, as
all trivial setters. That makes them decoupled from the type being
set. They neither know nor care nor anticipate what types should be
accepted in assignment. Hand-coded, old style setters make that
assumption with every overload.
Maybe 'unique_ptr' is bad example since it is usually conceptually
not attribute itself but merely a "unique reference" to actual
attribute. I trust it must work with 'T const&'. Anything must.
Does not below code work? (I haven't tested myself but it feels
it should):
I'm glad you posted that code, because it shows how the setter
overloading can be abused. Consider this modified code:
void copyX( XPtr const& x )
{
theX.reset( x ? new X(*x) : nullptr );
}
template<class T>
void setTheX(T &&x)
{
theX = std::forward<T>(x);
}
In the original version, setTheX() was misleading. It was not setting
a variable to a new value - it was copying it. unique_ptr implies
exclusive ownership and passing it to a setter usually means passing
the ownership. Would you expect a copy to be made? Even more - your
code assumes X is copyable. It is constrained by that type. You also
tied yourself to uniqe_ptr by assuming it's not copyable. What if it
wasn't unique_ptr but some other smart pointer? Would you tie yourself
to its interface? If shared_ptr was used instead, would you modify
the function body and change the logic? Too many assumptions made.
Now in the above example, the name was changed to explicitly state a
copy is made. The function clearly states its intention. On the other
hand the setter now does, what setters should do - it assigns a new
value to a variable. It behaves as someone would expect it to
behave; it is detached for the interface of unique_ptr and it is
future-proof. Changing the type to shared_ptr would result in no code
change in the setter, possibly only in use cases. Also you could do
the following:
setTheX(new X());
setTheX(XPtr(new X()));
setTheX(nullptr);
All those use cases work and are intuitive. All use specific
operator = overloads without knowing they exist at all. It is clean
and states the desired intention of setting theX to the specified
value in type-agnostic way.
I do not suggest to use 'T const&' as uniform way to pass parameters.
I merely pointed out that it is better candidate for such uniformity.
For me it is fine when conceptually different parameters are passed
differently and that the interfaces make it clear.
That leads to code duplication with all possible operator = overload.
How? You turn something in interface into generic (template) and that
somehow exposes something and breaks encapsulation? For me it loosens
interface (might be overly so designer likely has to add trait checks)
and does not expose nor break anything.
Using this trick on non-setter functions lead to the programmer
asking questions "why?". Why is that template there? Is it forwarded
to something else? Is something being set to it? Too many questions
lead to the interface being not clear enough. It leaks hints about
its inner workings. On the other hand, setters explicitly state their
intention and use cases.
The user of interface needs not only know types that can be passed in
but what the heck is that 'mWidget' and what set of 'operator=' its
type has. How it is "there is no need of any knowledge about it"?
The key word here is "user". The user has to know that type, because
he is using it. The setter does not need to know anything about that
type, other than supplying operator =. The class with the setter is
not constrained by that type. The user might be constrained, but in
such case, it's understandable, since he is the user of it. He should
know what he uses and how to use it. The class with the setter
doesn't need to. Do STL containers care or know about the type they
contain? Of course not - they only contain it. They only assume
the ability to be moved or copied, when the user uses some functions
which rely on such behavior. But then, the user is responsible for
that, not the container. Also consider emplace() functions. They act
on the same principle as template setters - take anything and try to
create an instance of the given type, with that parameter. It is the
responsibility of the user to know how to use them and what to pass.
Such abilities like "can be moved" or "can be copied" are part of
conceptual nature of type. If something like that changes in interface
then that is major change. Such things should not change out of blue.
Also like I pointed out the possible problem diagnostics are just moved
from functions signature into body of the setter so that can be hardly
viewed as a good solution for changes like you describe.
I agree with you - changing public interface is usually bad and
should be minimized. Yet, if that happens (unfortunately, it does),
template setters should be unaffected. They are already future-proof.
Traditional setters would need to change their interface, propagating
the problem further. Template setters only need their uses to adapt.
You make a good point about moving the diagnostics further down. It
is the same concern I have, which makes me feel uneasy. Yet I think
the benefits outweigh the problem.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]