Boost logo

Boost Users :

From: David Abrahams (dave_at_[hidden])
Date: 2005-03-06 12:07:31


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

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

> 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

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