Boost logo

Boost Users :

From: Peter Dimov (pdimov_at_[hidden])
Date: 2005-03-06 13:31:08


David Abrahams wrote:
> "Peter Dimov" <pdimov_at_[hidden]> writes:
>
>> David Abrahams wrote:
>>> "Peter Dimov" <pdimov_at_[hidden]> writes:
>>>
>> Well, the problem here is that (a non-macro) _1 can't be a value and
>> a type at the same time, not that the two _1s express different
>> notions.
>
> You can do it with a macro??

Yes, the famous "#define _1 arg1()" trick by Daniel Wallin.

>> My own position is that one should pick overloading or
>> specializations based on whether exact type matching better fits...
>> no, make that "is essential for" the design.
>
> IIUC, you're saying that if exact type matching is not _essential_,
> you'd never use specialization for customization? Wow. That would
> make it pretty rare. That leaves only cases where derived classes
> commonly need to use the default (primary template) customization when
> customizations have been made for their bases.

More likely, when derived classes need to fail compilation when a base is
customized but a derived is not. "swap" without a primary template, in other
words.

>> Once these customization points become well-known, more and more
>> types start to conform to them, and more and more other libraries
>> start to depend on the customization point. This effectively means
>> that this particular customization point can no longer be changed by
>> the original author, because this will break too much code. In some
>> cases it even changes the meaning of the customization point
>> (usually to a subset of the original).
>
> Okay, I understand that in theory, but have we seen it in practice?

We haven't seen identifier clashes in practice either. ;-) No, g++ doesn't
count.

Consider get_pointer. It was once "the protocol that mem_fn uses when faced
with a smart pointer" and was not required to return a raw pointer. Now it's
"the way to obtain a raw pointer from a pointer-like object" and is no
longer used by mem_fn (in its TR1 incarnation). If this isn't an example of
how the customization point is not owned by the library, I don't know what
is. ;-)

>> The implication of that is that customization points need to be
>> minimized
>
> Of course. Delay generalization and parameterization.
>
>> and their semantics - carefully chosen. They are even more
>> important than the (rest of the) public interface of the library,
>> because they can affect and shape the code of people that don't even
>> use this particular library.
>
> So does the rest of the public interface.

The rest of the public interface does not affect non-users.

> I'm not sure about the weight you're giving to these things. People
> supply
> these customization points at most once per component, and it's a "private
> matter between two consenting programmers." It doesn't need to affect
> other users of either programmers' code -- unless of course you have
> to worry about future name collisions ;-)

A customization point is a public protocol that needs to be followed by
types in order to advertize a certain property or operation. I don't see how
it can be a private matter. Library A introduces customization point f(x),
library B defines f(y) for its type Y, library C uses f(x) in order to
benefit from the fact that there are already types Y that support the
protocol. These three libraries are totally separate.

> Uh, wait. iterator_traits was not well designed, I'll grant you, but
> all the same, I'd call using iterator_traits<X>::reference to mean
> "the return type of *x" a definite no-no. Just think about the
> consequences of all those other type names in iterator_traits.

Well, what else could it mean? :-)

>> difference_type, the return type of the expression x - y, also
>> makes sense in a non-iterator context.
>
> Yeah, but this is clear abuse. If it wasn't clear, we'd have seen it
> done over and over. I haven't ever seen it.

Why is it a clear abuse?

>> In my experience - which is not that extensive -
>> well thought out customization points can be, and are, used outside
>> of the context of the library that created them.
>
> Maybe. You have examples?

get_pointer? intrusive_ptr_*?

>> They are a part of a domain to the extent that the type they are
>> associated with is part of that domain, not because of the
>> originating library.
>
> Let's see: get_pointer is part of the domain of pointer-like types.
> Not part of the domain of iterators and the domain of smart pointers.
>
> swap is part of the domain of fundamental value-type operations.
> There's no way that you'll convince me that swap is part of the domain
> of generalized containers and also of numeric types, etc., just
> because types from all those domains can be swapped. The abstract
> swap operation belongs to the domain of the things they have in
> common.
>
> Anyway, that's what I mean by "domain," so if you meant something
> else, please re-read what I wrote in newly-understood context.

Right. There is nothing that links get_pointer or swap to a particular
library. Whether get_pointer or swap makes sense for a type X is determined
solely by X, not by the library that happened to introduce get_pointer or
swap.

>> Example: I can use intrusive_ptr_add_ref to increment the reference
>> count of a type without ever including <boost/intrusive_ptr.hpp>. It
>> is a way for a type to advertize itself as intrusively counted in
>> general, not as suitable for use with boost::intrusive_ptr in
>> particular.
>
> Great, but it depends on the type implementing a particular protocol
> introduced by your library.

Right. But "introduced by" and "owned by" are not the same thing. Once it's
introduced and adopted, the genie is out of the bottle.

>> If every intrusively counted smart pointer has a different
>> customization interface, users are protected from collisions, but
>> need to mark their types as intrusively counted multiple times, to
>> satisfy each library.
>
> Yes. It's better if they all choose a common interface. That
> interface should be associated with a namespace.

If they all choose a common interface, why should it be protected by a
namespace? The identifier is now a de-facto standard. It doesn't need
protection.

>>>> OK; so what alternatives do we have?
>>>>
>>>> 1. lib1::numeric_traits<X>::zero( x ); // somewhat unwieldy
>>>> 2. lib1::zero( x ); // syntactic sugar for the above
>>>> 3. lib1_zero( x );
>>>>
>>>> What, exactly, are the advantages of #2 over #3?
>>>
>>> #3 invokes ADL.
>>
>> Yes, yes, but what are the advantages of #2 over #3? ;-)
>
> Ah, I see what you're getting at now. Maybe ADL customization points
> prefixed by the library name are the way to go, at least until the
> language gets better. That would make your thing
> boost_intrusive_ptr_add_ref unless you are intentionally trying to
> drive a stake in the ground for standardization.

I always do. A library that is not aiming to be a part of the standard is
not worth doing. ;-)

On a more practical note, I think that making the identifier "sufficiently
unique" is enough. boost_add_ref would be fine too, standardization
notwithstanding.

> Is that better or worse than the use of "domain tags?" You poked a
> hole in that technique a few years ago, but I don't remember how big a
> hole. I mean:
>
> namespace lib
> {
> struct tag {};
>
> template <class T>
> T zero(T a) { zero(tag,a); } // interface
> };
>
> namespace my
> {
> struct X {};
>
> X zero(lib::tag, X); // customization
> };
>
> my::X z = lib::zero(my::X());
>
> I realize it's not bulletproof, but it does narrow the possibile
> problems.

I don't like domain tags much. Seems like a fairly high price to pay for
dubious returns. I only use such tags when I need to inject a namespace for
ADL purposes via the tag.


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net