Re: Template setters
On Tuesday, 17 September 2013 00:10:02 UTC+3, Kamil Rojewski wrote:
Sorry for the late reply.
No problems, usenet is exactly good for low speed discussions.
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.
That is "us" in what shoes?
The "setter's designer" /must/ know design details of class she designs
and certainly the type of particular attribute of class that particular
method modifies. Also she should know the types of arguments that are
passed to all interface of the class. If part of the interface is template
then it should verify that the concepts match. How else? Does she deserve
to be freed of that knowledge?
The "setter's user" should know the types of arguments that may be
passed to the part of interface she uses. She may not know the types
used internally in class ... that is called "encapsulation". That is
broken since the interface depends on overloads of 'operator=' and
the errors about those are the only indication of types of expected
arguments to your setters.
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.
That logic leaves impression that you try to free class designers
from knowing (and caring about) the types of components of class?
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.
Again: "setter" will be decoupled from type it sets? What is the
benefit?
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);
}
I think that I see. When there is meaningful difference between copying
and moving the arguments then sure do not overload and have different
names. You still have that needless complication that I object against.
IOW if I needed what you wrote then I would write it like that:
void copyToTheX( XPtr const& x )
{
theX.reset( x ? new X(*x) : nullptr );
}
void moveToTheX( XPtr&& x )
{
theX = x;
}
In the original version, setTheX() was misleading. It was not setting
a variable to a new value - it was copying it.
Variable /was/ set to new value and it was not copied anywhere.
Argument was not moved but ... so what? It was not damaged in any way.
unique_ptr implies exclusive ownership and passing it to a setter
usually means passing the ownership.
Then why not to enforce it with 'T&&' parameter?
Would you expect a copy to be made? Even more - your
code assumes X is copyable. It is constrained by that type.
I do not use such /assumptions/ whatsoever. X was clearly /declared/
copyable in my code. For interface maker it /must/ be clear.
You also tied yourself to uniqe_ptr by assuming it's not copyable.
You did bring 'unique_ptr' as example of movable but not copyable
type yourself?
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?
I do not expose what I internally use, ever. If the class uses
internally 'auto_ptr' or raw pointer (for whatever legacy reasons)
or 'shared_ptr' (since my side of interface actually uses it in
shared way) then I may still use move-only interface with
'unique_ptr'. The concerns of implementer of class and user of class
should be separated.
Too many assumptions made.
I repeat you did bring 'unique_ptr' as example of movable but not
copyable type yourself? It was example as reply to that. If you need
example about something else then ask away.
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);
How often you need to switch between 'shared_ptr' and 'unique_ptr' as
internal design detail? For me those types were exactly made to get rid
of loose concept of C pointer. Raw pointer sometimes means
'unique_ptr' for single element, sometimes 'unique_ptr' for possibly
several elements, sometimes 'shared_ptr', sometimes 'weak_ptr',
sometimes 'vector' and sometimes 'iterator' and so on. However the
concepts are so far that it is unlikely that we need to switch it.
Also, If I need to switch the internal type then why should that
affect interface? If it was 'unique_ptr' then it stays 'unique_ptr'
even if I internally switch from 'auto_ptr' to 'vector'. These
two should be decoupled.
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.
Ok, maybe I am not convincing. Maybe you should look at standard library?
It contains exactly one classic "setter":
void std::ios::setstate(iostate state);
Some other things are close:
// C things
char* std::setlocale( int category, const char* locale);
int std::fesetenv(const fenv_t *penv);
void std::setbuf( FILE* stream, char* buffer );
// stream manipulators
/*unspecified*/ std::setiosflags(ios_base::fmtflags mask);
/*undefined*/ std::setw(int n);
// also 'setbase' and 'setprecision'
So there are no templated setters. There even are no overloaded setters.
"The trick" seems to be used also very rarely, for example 'packaged_task'
constructors do not care about if the 'F' is movable or copyable:
template <class F>
packaged_task(F&& f);
template <class F, class Allocator>
explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
On the other hand it sometimes uses overloads to allow implementers to
have different code in bodies:
basic_string(basic_string const&, const Allocator&);
basic_string(basic_string&&, const Allocator&);
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.
Certainly the authors do care.
Every good template class I have seen does carefully check and limit
conceptual nature of types passed in and if optimal behavior differs
with template attributes of different nature then there are often
specializations.
Sometimes they fail because making good templates (and specializations)
takes skills. That we see from peculiarities of 'vector<bool>' or
unusually weakly performing (in several implementations) 'vector<size_t>'.
But rest assured, they do care.
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.
What you mean? No way. Standard requires things like "T is
EmplaceConstructible into X from args. For vector and deque, T is also
MoveInsertable into X and MoveAssignable." for emplace method of sequence
containers. Good library implementation certainly checks such traits.
Standard may leave it loose for allowing low quality implementations
by saying that "not fulfilling requirements is undefined behavior" but
I can not follow that practice when writing my code. I can not say
to my end user that she may not enter value "42" to textbox "A" and
that it is "undefined behavior" (sometimes works sometimes not)
when she does. That is unthinkable.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]