Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2004-04-19 07:33:59


Andrea Torsello <torsello_at_[hidden]> writes:

> Sorry for the late reply: I have been away on Sunday. By the way,
> you guys are amazing:

I'm doubly flattered to be "pluralized" in that way! BTW, the message
you're replying to is a pretty old message in the scheme of things;
I've moved past much of what I said there.

> I go away for one day and you have already inserted my alternative
> approach in the sandbox and thoroughly tested it. WOW!

It's more a product of an unhealthy obsession than anything you should
be impressed by. Also, you may not believe that I got the alternative
approach right, since I don't use explicit copy ctors. See my other
message for an explanation, though.

> David Abrahams wrote:
>
>> None of what you wrote below is "showing an example". I meant,
>> please write down some code demonstrating that your technique "has
>> less interactions with other language features". I'm not sure what
>> "other" means in this case.
>
> I meant that the user has to know less about the internals of the
> approach when dealing with templated constructors and/or functions
> that distinguish between temps and non-temps.

I agree.

> Anyway, I am downloading the sandbox right now. I will creating a
> few tests and use cases to highlight the differences in usage
> patterns for the two approaches. If anything, this will be useful
> for documentation purpose. Will be back with as soon as my
> rent-paying job allows me to.

Looking forward to it.

>>> 1) You need to treat the templated conversion-copy-constructor I was
>>> talking about differently: add a second default parameter with
>>> enable_if_different or something like that.
>>
>> OK, so I'll supply disable_if_same. I'm not sure where that leaves
>> us, but I think my approach still handles more cases (including direct
>> initialization on most compilers) with a similar amount of syntactic
>> overhead.
>
> Your approach handles direct initialization in more cases,
> since my approach pretty much gives up moving direct initialization
> and hopes the compiler can perform return value optimization.
> In defense of my method I must say that this case is arguably
> the simples RVO possible, which, unfortunately, doesn't mean that
> it actually is simple nor that all compiler get it right,
> as your tests clearly show.

I've been trying to convince the EDG guys that it's "obvious" that
*if* you absolutely must choose between direct and copy initialization
for the RVO, the intuitive choice is to go for direct initialization,
because, well, it's "direct". That's the user perspective. The point
is that from the point-of-view of a standards wonk and compiler
implementor, there's no particular reason to think that RVO is easier
in the direct case than in the other one.

> As for the syntactic overhead, I believe that my approach has an
> edge here, since it minimizes the amount of adjustments the user
> has to go through in order to use it, but of course this is a
> matter of personal opinion and I am certainly biased ;)

I think you have a good point, actually. The two approaches each have
their own strengths apparently, and I'm less convinced of the
advantages of technique 1 than I used to be. OTOH, I'm not convinced
there's any advantage at all to using an explicit copy ctor.

>>> 2) When overloading a function between temporaries and
>>> non-temporaries you have to use the enable_if_equal trick on
>>> return values while the changes with my approach are restricted to
>>> the passed types
>>
>> What's the significance of that? It doesn't change the function's
>> returned type.
>
> No, but it is one more thing the user has to do differently when
> using the move library.

Yep.

>>> 3) Non temporaries overloads must be templated.
>>
>> I don't see that as a liability when the alternative is code
>> duplication or forwarding.
>
> Template code can be arbitrarily long and impacts compile-time
> on each compilation unit using it, forwarding is one-line long,
> almost certainly inlined by optimizing compilers, and has
> negligible impact on compile-time.

You're right; that's a real issue. Also templates will usually
generate duplicate code for const and non-const lvalues. If I were
wedded to technique 1, I would consider doing:

    // f.hpp
    template <class T>
    typename enable_if_same<T const, X const,void>::type
    f(T& x);

    template <>
    inline void f(X const& x);

    template <>
    inline void f(X& x) { return f<X const>(x); }

    // f.cpp
    template <>
    void f(X const& x)
    {
        // whatever
    }

Which I think should have an equivalent effect.

Maybe a nicer enabler should be provided, that allows us to write:

      typename enable_lvalue<T, X, void>::type // no "const"

The enablers get more interesting if you want to write a generic
function over a templated type Y<U>:

    template <class T>
    typename enable_if<is_Y<T>,void>::type
    f(T& x);

fortunately I have macros to generate is_Y (see
boost/python/detail/is_xxx.hpp). On the other hand, with technique 2
I think you can't write the generic function at all, because template
arguments have to match exactly:

   template <class T>
   void f(const_lvalue<Y<T> > x); // can never match a Y<T> argument

>>> If you want to provide explicit overload on another compilation unit
>>> you must explicitly overload for both X and X const.
                        ^^^^^^^^
>>
>> No, that won't work. The rvalue will bind to the X const& parameter.
>> It's as I said: you just omit the bodies and use explicit
>> instantiation.
>
> I was under the impression that template typee would always resolve
> to the same type that the templated code would instantiate, i.e.
> non-const lvalue would instantiate a X& version of the template
> function and hence it would bind only to a X& explicit
> instantiation.

I think you're possibly suggesting what I did in the example above?
I must've misinterpreted you because technically there's no overload
there.

>>> While all these are doable, they expose a lot of details of the
>>> internals of the library to the user. With my approach The only
>>> requirement for #1 is that the conversion-copy-constructor be
>>> explicit (something that you better do anyway).
>>
>> Which one is that, and why had you better do it anyway?
>
> template <class U>
> X(X<U> const& other);
>
> As for the fact that I believe that in most cases you should make it
> explicit: would you want a vector<int> be _IMPLICITLY_ convertible
> to a vector<double>?

Let's see, would you want a shared_ptr<Derived> to be _IMPLICITLY_
convertible to a shared_ptr<Base>? ;-) Seriously, most such
converting ctors I've seen in the wild are of that nature. I don't
think there's any way to generalize about whether explicit is usually
needed.

> You can always write the constructor as
>
> template<class U>
> X(X<U> const &, typename enable_if_different<T,U>::type=0)
>
> which would make it impossible for the constructor to resolve to
> X(X<T> const &)
>
>> You're right. That's why we need &&.
>
> Couldn't possibly agree more or more emphatically!

And, let me point out, we need T&& with "perfect forwarding" argument
deduction: T is U cv& when the argument is an lvalue, and plain U when
the argument is an rvalue. The reason, of course, is that having an
rvalue reference without it just compounds the forwarding problem
because now we have 3 kinds of reference to consider: T&, T const& and
T&&.

-- 
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk