Boost logo

Boost Users :

From: David Abrahams (dave_at_[hidden])
Date: 2005-03-06 18:16:00


"Peter Dimov" <pdimov_at_[hidden]> writes:

> 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.

I thought it might be something like that.

>>> 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.

Okay. Those are two of a kind.

>>> 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.

So maybe we should put everything in the global namespace?

> 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. ;-)

I don't know if citing a library evolution for which you hold all the
responsibility really counts. ;-)

>>> The implication of that is that customization points need to be
>>> minimized
>>> 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 don't get it. Even if author A has to glom a giant ugly fribsackle
onto his type so that it can satisfy library B's concept requirements,
author C can use A's type with B's functions without ever having to
confront the fribsackle herself.

>> 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.

You mean that the customization point is automatically up-for-grabs
for any library to use, as soon as you advertise that it makes a type
work with some particular library.

Well, I guess that's the difference between specialization and
overloading. With specialization, you sorta need to have one of that
library's headers around to get the declaration.

>> 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? :-)

Leaving aside the fact that in real life it is underspecified and used
inconsistently, it could mean "the return type of *x where x is an
iterator."

>>> 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?

Come, come, my man! Because of the way iterator_traits is named,
first of all. Also because of the way the primary template is defined.
The normal way to satisfy iterator_traits is to inject nested
typedefs. If you want to use it to get difference_type for all
subtractable types, you have to add a bunch of irrelevant typedefs,
including iterator_category. And specializing iterator_traits and
leaving out a type is almost as weird.

>>> 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_*?

Okay.

> 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.

No, it's also determined by the specification of requirements for
get_pointer and swap. These specifications came with a particular
library. get_pointer, in particular, seems like it could easily
collide someday.

>>> 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.

What is your meaning for "owned by?"

>>> 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.

Because people devlop in different domains. I can _easily_ imagine
some person writing a boost::addressof with the name get_pointer. If
they develop a big application in a single namespace with their own
smart pointers and put this get_pointer in that namespace, they might
have problems using Boost.

Of course then there's the argument that people really ought to be
making nice little granular namespaces to encapsulate types and their
ADL customizations. At least then, a case like the above won't occur
(a customization point colliding with a general utility function).
That only leaves customization points to collide with one another, and
only when a type is explicitly adapted to two libraries. In that case
I guess the author can resort to some kind of wrapper.

>> 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.
>
> On a more practical note, I think that making the identifier "sufficiently
> unique" is enough. boost_add_ref would be fine too, standardization
> notwithstanding.

Yeah, probably.

>> 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.

What's the cost, overall? You may "need" such an interface
dispatching function anyway, to deal with builtin types.

> I only use such tags when I need to inject a namespace for
> ADL purposes via the tag.

Example, please?

-- 
Dave Abrahams
Boost Consulting
www.boost-consulting.com

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