David Abrahams wrote:
"Peter Dimov"
writes: David Abrahams wrote:
"Peter Dimov"
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
. 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.