"Peter Dimov"
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??
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.
Potential identifier conflicts come a distant third to me. I understand the problem and the implications, I just don't give it that much weight.
It's not the only reason ADL makes me uneasy, as you know.
Not putting member functions in concept requirements is a well-known generic programming guideline. For years even Scott Meyers has been saying that "interfaces should be extended with free functions," which isn't viewed through the generic customization-point lens, but means the same thing.
Scott Meyers's point is that operations that can be expressed in terms of the public interface should be non-friends, which is not the same thing at all.
IMO it's a very closely related idea, IMO.
The primary problem with member functions (and types) as a concept requirement is that they can't be retrofitted to a type.
And that they don't work for builtins.
The fact that a type can't have two members with the same name
?? I can overload member functions.
is a secondary concern, which goes relatively unnoticed compared to the "ADL problem".
and the debate is whether not having to worry is a good thing. Customization points are very important and need to be treated with caution.
What kind of caution, if they are associated with a namespace, and why? We could use std::iterator_traits as an example.
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?
Once they become de-facto standards, the library no longer owns them, even if they are in the library's own namespace.
Even if I understood what you meant by "the library no longer owns them, even if they are in the library's own namespace," what are the implications of that? Is std::iterator_traits an example of a de-facto standard customization point in a namespace?
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. 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 ;-)
I see potential identifier collisions as just one of the things can go wrong, not as the only danger.
Oh, I fully agree.
To take iterator_traits<X>::reference as an example, a carefully chosen meaning for it would probably be "the return type of the expression *x", which isn't really domain specific. Once libraries start using it you can no longer turn back and redefine it as something that only makes sense for iterators.
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.
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.
There are clearly some (just a very few, IMO) customization points like swap that are really intrinsic. They have to do with the semantics of what Stepanov and friends are calling "value types." It seems to me as though the rest are associated with a particular domain, and it's appropriate to name that domain.
I can agree that most customization points are associated with a particular domain, but this doesn't mean that they are associated with a particular library from that domain.
Some library has to introduce them.
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?
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.
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.
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.
They would certainly prefer the smart pointer authors getting together, so to speak, and settling on one interface.
But you'll note that the function is not named add_ref, after all. ;-)
Hum, I do note that...
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. 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. -- Dave Abrahams Boost Consulting www.boost-consulting.com