|
Boost : |
From: Aleksey Gurtovoy (alexy_at_[hidden])
Date: 2001-11-27 15:40:01
Emily Winch wrote:
> So, there are functions, which expose a return value called
> "type", there are objects which expose a "type" because
> they can be used as functions, and there are objects which
> are definitely not functions and should not expose a
> return value.
Exactly.
> People who write _normal_ functions manage to cope somehow
> without naming their return value :)
Yeah, I wondered how I do it too :). IMO "normal" functions can be
classified into the same two categories as well: ones which return value's
type is obvious from the name of the function (i.e. which are in the same
category as add_const<>; "person.name()" is one example, although may be a
controversial one :), and the ones for which you infer the information about
their return value's type _mainly_ from the function's signature or its
usage context (or from reading the docs :); for example, if it was possible
to write:
std::pair<my_iterator,bool> res(map.insert(777));
as
auto typename T res(map.insert(777));
when one would have no chances to guess what type "res" is, without looking
at std::map<>::insert signature, or reading the docs (supposing that it's
the first time she came across this function, of course :).
So, the issue is pretty much the same in the "run-time world" as well, it's
only shaded a little bit by the language strong static type system that
forces you to write clues about function return type all over the place :).
Hmm, just realized that I haven't thought about dynamic-typed languages :).
Anyway, I guess you are right in sense that it's mainly a matter of
familiarity with the library; after you've used 'find<seq, T>' for a while,
'find<>::type' would be as clear to you as 'find<>::iterator', and the
former is shorter :).
> So long as it is clearly documented that "type" means "return
> value" and not "member variable named type", then I think the
> intention is not confusing. (other people's MMV...)
Hmm.. may be the ability to write both 'begin<seq>::iterator' as well as
'begin<seq>::type' is indeed a misfeature, and we should stick to simple and
consistent "::type" everywhere.. what do you think?
> Also useful would be a convention for denoting the return
> type of a runtime function, where that type depends on the
> template parameters of the function, like this
>
> struct X{
> template<class T> struct get_return_type{
> typedef typename long_calculation_with_T<T>::type type;
> };
> template<class T>
> typename get_return_type<T>::type
> operator()( /* whatever */ ){
> /* runtime stuff */
> }
> };
Yes, IMO handling both runtime and compile-time computations
simultaneously/in the same place makes a perfect sense, and often it's the
only way to do things effectively. In fact, chances are that I would write
the above as
template<class T>
struct X {
typedef typename calculation_with_T<T>::type type;
type operator()( /* whatever */ ){
/* runtime stuff */
}
};
FWIW, a while ago MPL had the following:
template<typename SequenceTag>
struct back_algorithm_traits
{
template<typename Sequence> struct algorithm
{
typedef /**/ type;
static type& result(Sequence& seq) { return /**/; }
static type const& result(Sequence const& seq) { return /**/; }
};
};
template<typename Sequence>
struct back
: back_algorithm_traits<
typename mpl::sequence_traits<Sequence>::sequence_category
>::template algorithm<Sequence>
{
};
template<typename Sequence>
typename mpl::back<Sequence>::type&
back(Sequence& seq)
{
return mpl::back<Sequence>::result(seq);
}
template<typename Sequence>
typename mpl::back<Sequence>::type const&
back(Sequence const& seq)
{
return mpl::back<Sequence>::result(seq);
}
but at some point I got rid of run-time 'result()' stuff in sequence
algorithms under the flag of simplification :). Needless to say that now I
think it was a mistake :).
> > One particular commonality that I was talking about is
> > (IMO) in tuple algorithms and iterators. For example, as
> > far as I can see, your 'for_each' and my 'for_each' do
> > pretty much the same thing, except that mine allows
> > function object to change its type on each step of
> > iteration (and ignoring the fact that my compile-time
> > check for iterators' equality is kind of strange :).
>
> Strange compared to which other compile-time iterator
> equality check? ;) I don't see any other way to do that:
> your way is exactly the same way as I came up with.
Well, at least yours is hided inside of the iterator type itself :).
> Unless anyone can think of a better way to compare
> equality of iterators at compile time (than comparing the
> type of the list they point at) I think we can enshrine that
> as the Normal Way To Do It.
It probably is.
>
> The thing where the function object itself can change type is
> actually a neater way to do what I've been trying to do all
> along. I had the function object calling a Thingy that the
> user could specialise.
I like your names :). I am afraid I don't have enough information to analyze
how different our _applications_ of the tuple algorithms are (but from what
you've said, it seems that they are be different), but my main motivation to
give a function object a possibility to change its type was to allow one to
accumulate internal state on each step of iteration; I guess I need to see
some examples to be able to grasp a need for specialization here :).
> _Some_ way of doing that kind of thing seems fundamental
> to tuple algorithms, since the bigger the tuple the more likely
> it is to contain a type that requires special treatment. Not to
> mention pointers and references which both also often seem
> to require special treatment.
>
> I shall have to do some thinking about that. Hmm.
Sure.
-- Aleksey
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk