Musings on customization points, ADL, and the loss of namespaces

141 views
Skip to first unread message

Nicol Bolas

unread,
Sep 23, 2017, 11:16:00 AM9/23/17
to ISO C++ Standard - Future Proposals
For the purposes of this conversation, a "customization point" refers to any mechanism used to add generic behavior to a type which didn't directly support it from outside of that type's definition.

As C++ has grown, we have started to develop customization point idioms. And the standard has adopted several of them. The very first was `std::swap`, and every new customization point since then has used the particular idiom of `std::swap. That is:

1: The standard library defines a function called `std::swap`, which provides default behavior.

2: Users can customize the point for a type via adding an ADL variant.

3: Users invoke the customization point by declaring `using std::swap;` (to bring in the default behavior) followed by ADL-style `swap(...)`.

Pretty much every customization point idiom ultimately relies on an ADL-based call. And that's the concern I want to deal with here: ADL calls.

The big problem with ADL customization is that it is possible for multiple libraries to have conflicting customization points due to name conflicts. And C++'s normal mechanism for dealing with such conflicts, namespaces, can't save you, since ADL is designed to ignore namespaces.

The Range TS uses a different idiom, based on a functor that you call. This idiom requires that you scope the call at the invocation site with the namespace. But for the actual customization, it still relies on ADL for out-of-class injection of functionality.

The problem we ultimately have is that ADL doesn't know or care about whether a particular function was meant for the purpose you're using it for. After all, ADL was created to make operator overloading in namespaces actually work without having to qualify everything. The set of operator functions is limited, and the number of parameters that they take is usually fixed.

ADL as the mechanism for injecting customization functions doesn't meet those criteria.

While the Range TS customization mechanism is superior to the standard library mechanism, it still falls short on connecting a specific function declaration with a specific customization point. And as C++ gets bigger, we keep adding new customization points. The Range TS has over a dozen. They cover much of the API of `std::vector`. As we add more generic mechanisms, we add more customization points.

It is disconcerting that we're building these generic mechanisms within what is effectively a single namespace.

Now, I don't have a solution to this (except for a dedicated language feature that might solve it, but I don't even have a sketch of a design for that). But I do feel that this will increasingly become a problem as new customization points are added, both to the standard library and more importantly to user libraries. Indeed, the Range TS customization idiom is so effective at removing most of the flaws of the (rightfully maligned) standard library approach that widespread use of it could exacerbate the problem.

Is this going to be a real problem going forward, or are these fears unfounded?

Aarón Bueno Villares

unread,
Sep 23, 2017, 5:54:12 PM9/23/17
to ISO C++ Standard - Future Proposals
I wrote a couple of days ago to Eric Niebler about the same issue, and he tolds me that the risk "is not so big". My points were:
  • Namespaces were born to avoid name conflicts, and customization points takes alternative paths to remove them
There is some risk, yes, although I think it's small. The boost range library chose to call it's range-related customization points `range_begin` and `range_end` for this reason. I expect the number of genuine std-related customization points to be small. `swap`, `begin`, `end`, `size`, `iter_swap`, `iter_move` is the current list, I think. We can live with telling users not to define free functions called that. As for customization points defined by 3rd party libs, a solution like you suggest or like the one used by Boost would be worthwhile.
  • Standard customization points work like keywords to the language due to name conflicts
To a limited extent. Users are free to have functions begin and end that take more than one argument, for instance. But yes, for all intents and purposes, swap has been a keyword since C++98, and begin and end since ... TR1? I haven't heard cries of outrage, have you?

I'm far from being a C++ expert but in my understanding I agree that the issue maybe is not big. Only by recommending 3rd party libraries to don't use those names with that exact number of parameters would be enough, as far as the number of customization points is kept short.
 
Anyway, the solution I purposed was just to add and extra parameter of type, std::tag, for example, or std::customize inheriting from std::tag (imitating the phylosophy of std::exception and its hierarchy), to make the source namespace present as a last user's customization point parameter (which will remove the need of "poison pills" too).

Besides, a standard tag hierarchy could open new possibilities, like adding the concept check within the tags. That way, users, even learners, can test in a very easy way if its types meet the requirements just by instantiating the corresponding concept tag with its own type and see the generated compiler errors, which can besides be used for making simpler to end users write overloads by concepts instead of by type.

inkwizyt...@gmail.com

unread,
Sep 24, 2017, 5:19:21 AM9/24/17
to ISO C++ Standard - Future Proposals


On Saturday, September 23, 2017 at 11:54:12 PM UTC+2, Aarón Bueno Villares wrote:

Anyway, the solution I purposed was just to add and extra parameter of type, std::tag, for example, or std::customize inheriting from std::tag (imitating the phylosophy of std::exception and its hierarchy), to make the source namespace present as a last user's customization point parameter (which will remove the need of "poison pills" too).

I think this is interesting solution, nobody will by mistaken using this tag and name conflict will be fix by overloads:
namespace std
{

class customize_t {} customize;

template<typename T>
void swap(customize_t, T a, T b);

template<typename T>
void swap(T a, T b)
{
    swap
(customize, a, b);
}


}

namespace My
{

class A;
void swap(A, A); //backward compatible version for C++03 code
void swap(std::customize_t, A a, A b); //new customization point

}


Reply all
Reply to author
Forward
0 new messages