Boost logo

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