Solving the std::swap embarrassment*?

496 views
Skip to first unread message

Marc Mutz

unread,
May 2, 2017, 2:25:03 PM5/2/17
to std-pr...@isocpp.org
* technical term, not judgmental

Hi,

I thought this was solved in C++11 but Ville proved me wrong today:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#229 motivates
partial function specialisation for solving the problem that the world outside
namespace std has to write

using std::swap;
swap(a, b);

(where swap() is a stand-in for any std function, incl. e.g. sin(),
for_each()). They can't just write

std::swap(a, b);

because class authors are forbidden by [namespace.std] to add new declarations
(that includes function overloads) to namespace std, even though they are
allowed to partically specialise class templates there. You cannot partially
specialise function templates, you need to overload them. But you can't. Not
in namespace std. So users need to activate ADL, while still allowing falling
back to the std version, thus the using declaration + unqualified name lookup.

I don't know when I learned about this the first time, but I do know from
where: Scott Meyer's books. Those are ~20 years old by now.

Can't we do better than forcing the world to permanently add that

using std::name;

for essentially any call to a function in namespace std?

Can't [namespace.std] be extended to allow function overloads as a kin to the
(allowed) partial class temlate specialisations? With the same caveats? Must
use user type, must actually work as the std versions?

Or, alternatively, can't

int i, j;
swap(i, j);

automagically find the std version?

Thanks,
Marc

--
Marc Mutz <marc...@kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
Tel: +49-30-521325470
KDAB - The Qt, C++ and OpenGL Experts

Ville Voutilainen

unread,
May 2, 2017, 3:31:52 PM5/2/17
to ISO C++ Standard - Future Proposals
On 2 May 2017 at 21:23, Marc Mutz <marc...@kdab.com> wrote:
> Can't [namespace.std] be extended to allow function overloads as a kin to the
> (allowed) partial class temlate specialisations? With the same caveats? Must
> use user type, must actually work as the std versions?

..must be an overload of an existing function specified by the
library, must not change
any overload resolution results, I think. Something like that makes
sense to me when I
vaguely remember the days when I was a mere language user, while still
remembering
why the restrictions that we currently have are there.

> Or, alternatively, can't
>
> int i, j;
> swap(i, j);
>
> automagically find the std version?


I bet that idea breaks some code somewhere.

Nicol Bolas

unread,
May 2, 2017, 6:09:06 PM5/2/17
to ISO C++ Standard - Future Proposals
On Tuesday, May 2, 2017 at 2:25:03 PM UTC-4, Marc Mutz wrote:
* technical term, not judgmental

Hi,

I thought this was solved in C++11 but Ville proved me wrong today:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#229 motivates
partial function specialisation for solving the problem that the world outside
namespace std has to write

   using std::swap;
   swap(a, b);

(where swap() is a stand-in for any std function, incl. e.g. sin(),
for_each()). They can't just write

    std::swap(a, b);

because class authors are forbidden by [namespace.std] to add new declarations
(that includes function overloads) to namespace std, even though they are
allowed to partically specialise class templates there. You cannot partially
specialise function templates, you need to overload them. But you can't. Not
in namespace std. So users need to activate ADL, while still allowing falling
back to the std version, thus the using declaration + unqualified name lookup.

I don't know when I learned about this the first time, but I do know from
where: Scott Meyer's books. Those are ~20 years old by now.

Can't we do better than forcing the world to permanently add that

   using std::name;

for essentially any call to a function in namespace std?

It's not "any call". It's only for calls where users would reasonably be expected to provide overloads for their types. So that means just `swap`.

And `begin` and `end`. And `cbegin` and `cend`. And `rbegin/rend`. And `crbegin/crend`. And `size` ;)

The problem really is that we want interfaces to allow 3 things though the same syntax, and C++ doesn't really support this combination very well:

1) Users can specify the behavior of these functions via member functions.

2) Users can specify the behavior of these functions for types via functions that are not members of that type.

3) Fundamental types can have defaults applied.

The problem is really #1 and #3. Both of these are important, and both of them are provided by the same overload. And neither of them use ADL

The solution to this insanity was supposed to be unified function call syntax, but the standards committee took a knee on that and refuses to even think about fixing it anymore.

So no, it's not gonna be fixed.


Or, alternatively, can't

  int i, j;
  swap(i, j);

automagically find the std version?

Even ignoring what breakage that could cause, it would still not be enough. Remember that #1 is implemented through the same mechanism: `std::swap` calling the type's member function. So if you have a type that provides a member `swap` but it doesn't have a `swap` at namespace scope (and let's be frank, it's stupid to have both), you still need `using std::swap` to get an unqualified call to `swap` to call the member function.

Again, UFC would have fixed this, but we cannot have nice things in C++.

Tony V E

unread,
May 2, 2017, 7:30:29 PM5/2/17
to Standard Proposals
And we keep adding to the list, with no end in sight.
 

The problem really is that we want interfaces to allow 3 things though the same syntax, and C++ doesn't really support this combination very well:

1) Users can specify the behavior of these functions via member functions.

2) Users can specify the behavior of these functions for types via functions that are not members of that type.

3) Fundamental types can have defaults applied.

The problem is really #1 and #3. Both of these are important, and both of them are provided by the same overload. And neither of them use ADL

The solution to this insanity was supposed to be unified function call syntax, but the standards committee took a knee on that and refuses to even think about fixing it anymore.

So no, it's not gonna be fixed.


There are many potential fixes.  One simple one that just doesn't look "elegant":

namespace std {
  template<typename T>
  auto foo(T t) { return std_foo(t); }  // plus forwarding, etc
}

Users call std::foo(),
Extenders implement std_foo() for their type(s).  Found by ADL.

This is basically an application of Herb's "Non virtual interface" pattern (ie make virtuals private http://www.gotw.ca/publications/mill18.htm), just at a different level.

Tony

 

Or, alternatively, can't

  int i, j;
  swap(i, j);

automagically find the std version?

Even ignoring what breakage that could cause, it would still not be enough. Remember that #1 is implemented through the same mechanism: `std::swap` calling the type's member function. So if you have a type that provides a member `swap` but it doesn't have a `swap` at namespace scope (and let's be frank, it's stupid to have both), you still need `using std::swap` to get an unqualified call to `swap` to call the member function.

Again, UFC would have fixed this, but we cannot have nice things in C++.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/dbbdbc10-3f83-48ea-9a78-beb179bd113a%40isocpp.org.



--
Be seeing you,
Tony

Arthur O'Dwyer

unread,
May 2, 2017, 10:08:12 PM5/2/17
to ISO C++ Standard - Future Proposals
On Tuesday, May 2, 2017 at 4:30:29 PM UTC-7, Tony V E wrote:
On Tue, May 2, 2017 at 6:09 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 2:25:03 PM UTC-4, Marc Mutz wrote:
* technical term, not judgmental

Hi,

I thought this was solved in C++11 but Ville proved me wrong today:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#229 motivates
partial function specialisation for solving the problem that the world outside
namespace std has to write

   using std::swap;
   swap(a, b);
[...]
Can't we do better [...] ?

It's not "any call". It's only for calls where users would reasonably be expected to provide overloads for their types. So that means just `swap`.
And `begin` and `end`. And `cbegin` and `cend`. And `rbegin/rend`. And `crbegin/crend`. And `size` ;)

And we keep adding to the list, with no end in sight.
[...]
There are many potential fixes.  One simple one that just doesn't look "elegant":

namespace std {
  template<typename T>
  auto foo(T t) { return std_foo(t); }  // plus forwarding, etc
}

Users call std::foo(),
Extenders implement std_foo() for their type(s).  Found by ADL.

Eric Niebler has done some proposal-work in this area. I think I've also chatted with Vicente Botet and Eric Fiselier on the subject. The search term you're looking for is "customization points."
IMHO, the most elegant solution (basically Eric Niebler's proposal) would involve MASSIVE breakage of old code and is thus suited only for "std2".

namespace std2 {
inline auto swap = [](auto& x, auto& y) constexpr -> void {
    if constexpr (__has_adl_swap(x, y)) {
        swap(x, y);
    } else if constexpr (__has_swap_member_function(x, y)) {
        x.swap(y);
    } else {
        auto temp = x;
        x = std::move(y);
        y = std::move(temp);
    }
};
} // namespace std2

(My intention with the __has_adl_swap and __has_swap_member_function pseudo-traits is that they should return constexpr true iff the corresponding expressions are well-formed.)

Notice that because this "std2::swap" is an object, not a function:
- you can pass it to an STL function template without any hassle (as opposed to the punctuation soup of std::less<>{})
- there is no temptation to "specialize" it via reopening namespace std2 (as there is, dangerously, with std::swap)
- there is no temptation to "overload" it via reopening namespace std2 (as there is, fatally, with std::swap)

Notice also that:
- it can swap heterogeneous types: I have not thought deeply about this aspect but I don't immediately see anything wrong with it
- it automagically works with your x.swap(y) member functions, thus you can avoid implementing ADL swap if you want (as opposed to today's best practice: implementing ADL-swap-in-terms-of-member-swap)

–Arthur

Nicol Bolas

unread,
May 2, 2017, 10:26:19 PM5/2/17
to ISO C++ Standard - Future Proposals

I'm not sure I entirely agree with the specific implementation, but this kind of thing really sounds like something that ought to be supported in the language in some way. I justify that by pointing out that the language actually does support this in two places: range-based `for` and structured binding, in searching for `begin/end` and `get`, respectively.

Note that in both cases, there is a also a default version. Ranged-based `for` has specialized behavior for C++ array types, and structured binding has special behavior for both arrays and publicly accessible structures.

This is also why I don't agree with the specific implementation, since non-member functions have overriding priority over members. By contrast, both `for` and structured binding give members priority.

If language features like range-based `for` and structured binding can do this specific kind of thing, then it seems clear that users of the language ought to be able to set such things up as well. And not with ad-hoc solutions like the above, but with some kind of real language feature.

Maybe to avoid the UFC issues, it would work like this. We provide a way to call a function (which is distinct from regular call syntax, thus pacifying the anti-UFC people) where you specify at the call cite that you're using the special lookup rules (check members, then check ADL, and if neither of them work, use the default). And you provide a way to define a function which represents one of the default cases when calling via these special lookup rules (otherwise, it acts like a regular function).
 

(My intention with the __has_adl_swap and __has_swap_member_function pseudo-traits is that they should return constexpr true iff the corresponding expressions are well-formed.)

Notice that because this "std2::swap" is an object, not a function:
- you can pass it to an STL function template without any hassle (as opposed to the punctuation soup of std::less<>{})

Or we could just have lifting lambdas and deal with it that way. Whatever happened to P0119, anyway?
 
- there is no temptation to "specialize" it via reopening namespace std2 (as there is, dangerously, with std::swap)
- there is no temptation to "overload" it via reopening namespace std2 (as there is, fatally, with std::swap)

A good language feature for customization points would remove such temptations as well.

Arthur O'Dwyer

unread,
May 2, 2017, 11:16:31 PM5/2/17
to ISO C++ Standard - Future Proposals
On Tue, May 2, 2017 at 7:26 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 10:08:12 PM UTC-4, Arthur O'Dwyer wrote:
Eric Niebler has done some proposal-work in this area. I think I've also chatted with Vicente Botet and Eric Fiselier on the subject. The search term you're looking for is "customization points."
IMHO, the most elegant solution (basically Eric Niebler's proposal) would involve MASSIVE breakage of old code and is thus suited only for "std2".

namespace std2 {
inline auto swap = [](auto& x, auto& y) constexpr -> void {
    if constexpr (__has_adl_swap(x, y)) {
        swap(x, y);
    } else if constexpr (__has_swap_member_function(x, y)) {
        x.swap(y);
    } else {
        auto temp = x;
        x = std::move(y);
        y = std::move(temp);
    }
};
} // namespace std2

I'm not sure I entirely agree with the specific implementation, but this kind of thing really sounds like something that ought to be supported in the language in some way. I justify that by pointing out that the language actually does support this in two places: range-based `for` and structured binding, in searching for `begin/end` and `get`, respectively.

FWIW: Structured binding actually searches for a whole mess of names, including `tuple_size` and `tuple_element` (both of which it looks up only in namespace std). So IMO "structured binding" should be an item on the ever-growing list of "ill-advised places where a customization-point feature was clearly needed but never actually designed," and not on the list of "nice things to copy the design of."


[...] This is also why I don't agree with the specific implementation, since non-member functions have overriding priority over members. By contrast, both `for` and structured binding give members priority.

That's fair. Consider the specific implementation hereby amended to prefer member x.swap(y) over non-member swap(x,y).

 
If language features like range-based `for` and structured binding can do this specific kind of thing, then it seems clear that users of the language ought to be able to set such things up as well. And not with ad-hoc solutions like the above, but with some kind of real language feature.

We agree that regular users ought to be able to set up customization points for their own stuff; indeed, basically every library in existence already has to solve the customization-point problem one way or another.  The problem with making customization points like std::swap into "language features" is that (AFAICT) that seems to point in the direction of a solution that is more special-cased and harder for regular users to adapt for their own libraries.

If I can solve the problem with an idiom that relies only on existing C++17 language features, then surely it would be a bad idea to come up with an idiom that relies on non-existent language features; especially if we then had to propose those language features and ram them through EWG motivated only by this new swap idiom?


Maybe to avoid the UFC issues, it would work like this. We provide a way to call a function (which is distinct from regular call syntax, thus pacifying the anti-UFC people) where you specify at the call site that you're using the special lookup rules (check members, then check ADL, and if neither of them work, use the default). And you provide a way to define a function which represents one of the default cases when calling via these special lookup rules (otherwise, it acts like a regular function).

I have no really smart things to contribute here, but I'll note in passing that this sounds like the sort of thing that reflection might be good at; e.g. we could write a template my::specialcall taking a compile-time string and using reflection to figure out whether any member function by that name actually existed —

    my::specialcall<"swap"sl>(x, y);

— except for the above's high probability of breaking cross-referencing utilities like Kythe by hiding function names inside things that look like arbitrary string values. (Which might already be a known issue blocking reflection, for all I know.)


Notice that because this "std2::swap" is an object, not a function:
- you can pass it to an STL function template without any hassle (as opposed to the punctuation soup of std::less<>{})

Or we could just have lifting lambdas and deal with it that way. Whatever happened to P0119, anyway?

No idea. I've been toying with the idea of writing a real proposal for the []foo syntax for Albuquerque, but I've got too many other things going on to actually get started on that.
 

- there is no temptation to "specialize" it via reopening namespace std2 (as there is, dangerously, with std::swap)
- there is no temptation to "overload" it via reopening namespace std2 (as there is, fatally, with std::swap)

A good language feature for customization points would remove such temptations as well.

Yes; or to put it equivalently, "If a [language] feature does not remove such temptations, then it is not good."  We agree on this.  I merely listed those bullet points to demonstrate that my/Eric's proposed feature isn't conspicuously not-good. (But logically, it might be not-good for some other reason.  "Removing such temptations" is a necessary but insufficient condition for goodness.)

Arthur

Nicol Bolas

unread,
May 3, 2017, 12:13:16 AM5/3/17
to ISO C++ Standard - Future Proposals
On Tuesday, May 2, 2017 at 11:16:31 PM UTC-4, Arthur O'Dwyer wrote:
On Tue, May 2, 2017 at 7:26 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 10:08:12 PM UTC-4, Arthur O'Dwyer wrote:
Eric Niebler has done some proposal-work in this area. I think I've also chatted with Vicente Botet and Eric Fiselier on the subject. The search term you're looking for is "customization points."
IMHO, the most elegant solution (basically Eric Niebler's proposal) would involve MASSIVE breakage of old code and is thus suited only for "std2".

namespace std2 {
inline auto swap = [](auto& x, auto& y) constexpr -> void {
    if constexpr (__has_adl_swap(x, y)) {
        swap(x, y);
    } else if constexpr (__has_swap_member_function(x, y)) {
        x.swap(y);
    } else {
        auto temp = x;
        x = std::move(y);
        y = std::move(temp);
    }
};
} // namespace std2

I'm not sure I entirely agree with the specific implementation, but this kind of thing really sounds like something that ought to be supported in the language in some way. I justify that by pointing out that the language actually does support this in two places: range-based `for` and structured binding, in searching for `begin/end` and `get`, respectively.

FWIW: Structured binding actually searches for a whole mess of names, including `tuple_size` and `tuple_element` (both of which it looks up only in namespace std). So IMO "structured binding" should be an item on the ever-growing list of "ill-advised places where a customization-point feature was clearly needed but never actually designed," and not on the list of "nice things to copy the design of."

To be honest... how else could it work? Oh sure, you might have found some way to make `tuple_size` be a `constexpr` function or something (though how you pass a parameter of type `E` to allow ADL without actually having to create an object of that type would be an interesting trick). But `tuple_element` has got to resolve to a type. And ADL just doesn't exist for meta-functions.

Now this actually argues strongly for a more comprehensive approach to customization points as a language feature. One that can include both runtime and compile-time function customization. This would essentially allow a user to invoke the rules of ADL to look up the exact template being instantiated.

[...] This is also why I don't agree with the specific implementation, since non-member functions have overriding priority over members. By contrast, both `for` and structured binding give members priority.

That's fair. Consider the specific implementation hereby amended to prefer member x.swap(y) over non-member swap(x,y).
 
If language features like range-based `for` and structured binding can do this specific kind of thing, then it seems clear that users of the language ought to be able to set such things up as well. And not with ad-hoc solutions like the above, but with some kind of real language feature.

We agree that regular users ought to be able to set up customization points for their own stuff; indeed, basically every library in existence already has to solve the customization-point problem one way or another.  The problem with making customization points like std::swap into "language features" is that (AFAICT) that seems to point in the direction of a solution that is more special-cased and harder for regular users to adapt for their own libraries.

If I can solve the problem with an idiom that relies only on existing C++17 language features, then surely it would be a bad idea to come up with an idiom that relies on non-existent language features; especially if we then had to propose those language features and ram them through EWG motivated only by this new swap idiom?

Um, no, it wouldn't be a bad idea at all.

Consider the code you just showed me. I pointed out a deficiency in it: the fact that it works the wrong way, relative to the hard-coded customization points. And that was written by an actual C++ expert, which means that it's very easy for a novice to get this idiom wrong. And a novice is far less likely to realize they got it wrong. After all, in 99% of the cases, it'll still work. It just has a subtle bug in it, which is worse in many respects than being straight-up broken.

Having all customization points work the same way is really important. Relying on everyone to use an idiom like this in exactly the same way is essentially begging to fail. Oh sure, the standard library can mandate the behavior of its customization points. But users need to be able to build their own customization points, and to do so in a way that works just like standard library ones.

And that's assuming that users are clever enough to implement `__has_adl_swap` and `__has_swap_member_function` correctly in the first place. I'm no metaprogramming expert, but I have no idea how to even begin implementing the ADL check.

Just as Boost.Lambda helped prove that lambdas ought to be a language feature, I think the weaknesses of this idiom (which is the best customization interface we've been able to create thus far) helps show that this ought to be a language feature too.

Arthur O'Dwyer

unread,
May 3, 2017, 1:17:34 AM5/3/17
to ISO C++ Standard - Future Proposals
On Tue, May 2, 2017 at 9:13 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 11:16:31 PM UTC-4, Arthur O'Dwyer wrote:

FWIW: Structured binding actually searches for a whole mess of names, including `tuple_size` and `tuple_element` (both of which it looks up only in namespace std). So IMO "structured binding" should be an item on the ever-growing list of "ill-advised places where a customization-point feature was clearly needed but never actually designed," and not on the list of "nice things to copy the design of."

To be honest... how else could it work? Oh sure, you might have found some way to make `tuple_size` be a `constexpr` function or something (though how you pass a parameter of type `E` to allow ADL without actually having to create an object of that type would be an interesting trick). But `tuple_element` has got to resolve to a type. And ADL just doesn't exist for meta-functions.

In the case of structured binding, the Right Thing would have been to not rely on tuple_element at all. They could easily have made

    auto [x,y] = t;

equivalent to

    auto __t = t;
    auto&& x = get<0>(__t);  // with compiler magic to make decltype(x) appear as a non-reference type, of course
    auto&& y = get<1>(__t);

without any reference to tuple_element.  (And notice that the rewrite doesn't explicitly use tuple_size, either. tuple_size is needed only to generate annoying compiler diagnostics in the case that t "has" more than 2 elements. How to bind only the first K ordinates of a tuple and discard the rest is a perennial topic of discussion on this mailing list.)


[...] This is also why I don't agree with the specific implementation, since non-member functions have overriding priority over members. By contrast, both `for` and structured binding give members priority.

That's fair. Consider the specific implementation hereby amended to prefer member x.swap(y) over non-member swap(x,y).

[...] Consider the code you just showed me. I pointed out a deficiency in it: the fact that it works the wrong way, relative to the hard-coded customization points. And that was written by an actual C++ expert, which means that it's very easy for a novice to get this idiom wrong. And a novice is far less likely to realize they got it wrong. After all, in 99% of the cases, it'll still work. It just has a subtle bug in it, which is worse in many respects than being straight-up broken.

I'd argue that if we had proper customization points (e.g. these inline-variable-based ones), then the difference between "subtle bug" and "perfectly correct" would become academic — I'm inclined to go further and say "would become nonsensical." The "subtle bug" you identified would cause problems during execution if-and-only-if:

- The user provided x.swap(y) with functionality 1
- The user provided an ADL swap(x, y) with functionality 2, observably different from functionality 1
- The user called std2::swap(x, y) expecting to receive functionality 1

I claim that bullet points #2 and #3 are deep into "well, what did you expect to happen?"-land, even in C++03. When you provide two swap functions, you'd better make darn sure that their behaviors are identical; and even if by accident their behaviors are different, you'd better not write code that relies on that bug!

If we had std2::swap today, there would be no reason for anyone to implement ADL swap(x, y) ever again; not even to implement it in terms of x.swap(y).  Forget having two swap functions with different behaviors; there'd be no reason to provide two swap functions period.  You'd simply implement your swap function as x.swap(y), and call it as std2::swap(x, y), and that would be all.


Having all customization points work the same way is really important. Relying on everyone to use an idiom like this in exactly the same way is essentially begging to fail. Oh sure, the standard library can mandate the behavior of its customization points. But users need to be able to build their own customization points, and to do so in a way that works just like standard library ones.

Just like the state of C++03 iterators (begin() and end()), right?  I claim that users who need to implement their own iterators can in fact do so, and users who need to implement their own customization points will in fact be able to do so. Just as with iterators, the user's solution will probably involve some cut-and-paste from the standard library. (Today we can't cut-and-paste customization points from the standard library because the standard library has no solution for customization points. But if we added std2::swap, then it would have a solution.)

And that's assuming that users are clever enough to implement `__has_adl_swap` and `__has_swap_member_function` correctly in the first place. I'm no metaprogramming expert, but I have no idea how to even begin implementing the ADL check.

I think it's easy, as long as you don't care about swap functions that show up in the global namespace (not in the ADL namespace in question) after the inclusion of <std2_swap>. (It's unclear to me whether functions in the global namespace ought to be considered "ADL" or not. Anyway, this is another case of "well, what did you expect to happen?".)

 
Just as Boost.Lambda helped prove that lambdas ought to be a language feature, I think the weaknesses of this idiom (which is the best customization interface we've been able to create thus far) helps show that this ought to be a language feature too.

That's a fair analogy; but I still can't picture what kind of core language feature would help here.  With lambdas it was pretty obvious what the core feature was; the only bikeshedding was over the spelling of []. With customization points, it's not real clear what we're trying to spell in the first place.  A new call syntax?  A new way of doing name lookup?  A new way of defining functions?

Here's an idea inspired by that "core language feature" starting point.  Here customization_point is a contextual keyword that means "Compiler, please wrap this function's body in boilerplate so that it'll call <firstarg>.<functionname>(<otherargs>) if possible; or delegate to the <functionname> found by ADL if possible; or else fall back on this function's body as the default implementation."  (The function's body might reasonably be =delete'd.)

    namespace std3 {
        template<class T>
        auto swap(T& a, T& b) customization_point {
            auto temp = std::move(a);
            a = std::move(b);
            b = std::move(temp);
        }
    } // namespace std3

    std3::swap(i, j);  // implicitly attempts to call i.swap(j) instead, if it exists

    using std3::swap;
    swap(m, n);  // plain old unqualified name lookup; but it might find std3::swap,
         // in which case we implicitly attempt to call m.swap(n) instead, and so on

Arthur

ol...@join.cc

unread,
May 3, 2017, 4:13:49 AM5/3/17
to ISO C++ Standard - Future Proposals
Op woensdag 3 mei 2017 04:08:12 UTC+2 schreef Arthur O'Dwyer:
IMHO, the most elegant solution (basically Eric Niebler's proposal) would involve MASSIVE breakage of old code and is thus suited only for "std2".

Wouldn't it work as std::swap2? 

Marc Mutz

unread,
May 3, 2017, 5:16:08 AM5/3/17
to std-pr...@isocpp.org
Sorry for highjacking Olaf's reply. This is not about Olaf's answer.

While it's true that std::swap is the most-prominent example, it's also one
which really is trivial in many ways. Few are the types which these days of
move semantics _need_ a custom swap(). Unless you still need to support C++98
compilers, it's merely a questionable optimisation technique (the best you can
do it member-wise swapping, which apart from a little less memory used, is
hardly orders of magnitude faster than what the triple-move default
implementation does).

No, forget swap. The Swap Problem is not (just) about swap. Much more
interesting than swap() are those functions which are API enablers:

A class template representing angles _needs_ a sin() overload, and up pops the
question how to provide it: via ADL, even though most people will likely not
write

using std::sin;
return sin(a);

? Or as a specialisation of std::sin() (doen't work) or as an overload of
std::sin() (not allowed). Example: https://codereview.qt-project.org/191717
So, yeah, via ADL, and thus continues the embarrassment...


A non-std container should provide a version of LF v2's erase_if(). Even
though erase_if is only overloaded on types in namespace std, since it's
currently in namespace std::experimental, you still can't say

erase_if(c, p);

ie. rely on ADL, but need to have another using declaration:

using std::experimental::erase_if;
erase_if(c, p);

This stuff usually gets a laugh from non-C++ experts already.
But note how this using declaration will fail to compile when LF v2 is not
supported, requiring adding preprocessor magic around it:

#if defined(_cpp_lib_dunno_exact_name) && \
__cpp_lib_dunno_exact_name >= 201411
using std::experimental::erase_if;
#endif
erase_if(c, p);

Ok, all of this will be solved when erase_if moves into namespace std proper,
making this a bad example.

Let's look at STL algorithms:

A wonky container like QList can actually implement reversing as an out-of-
line function: https://codereview.qt-project.org/144071 It's not far-fetched
to want to specialise std::reverse() to call the member function, which would
require overloading std::reverse(). Or providing an ADL version, which,
however, requires every use of std::reverse() to say

using std::reverse;
reverse(b, e);

Here, we finally reached a case where no-one in the world writes code like
this.

As you can see, the issue is not at all restricted to swap(), begin(), size(),
data() and the very few other customisation points that are being discussed.
Do you seriously want to make a std::sin2, std::reverse2, ... for every single
function in namespace std?

So I wonder: What's the rationale for not allowing adding overloads under the
same conditions as allowing partial specialisations of class templates? If a
user messes up, how is it a worse mess with overloads than with class template
specialisations? What's so scary about it that you'd rather discuss _new
language features_ to enable customisation than to allow it by what C++
provides for ages, and even novices quickly grasp: overloads?

T. C.

unread,
May 3, 2017, 6:16:25 AM5/3/17
to ISO C++ Standard - Future Proposals


On Wednesday, May 3, 2017 at 1:17:34 AM UTC-4, Arthur O'Dwyer wrote:
On Tue, May 2, 2017 at 9:13 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 11:16:31 PM UTC-4, Arthur O'Dwyer wrote:

FWIW: Structured binding actually searches for a whole mess of names, including `tuple_size` and `tuple_element` (both of which it looks up only in namespace std). So IMO "structured binding" should be an item on the ever-growing list of "ill-advised places where a customization-point feature was clearly needed but never actually designed," and not on the list of "nice things to copy the design of."

To be honest... how else could it work? Oh sure, you might have found some way to make `tuple_size` be a `constexpr` function or something (though how you pass a parameter of type `E` to allow ADL without actually having to create an object of that type would be an interesting trick). But `tuple_element` has got to resolve to a type. And ADL just doesn't exist for meta-functions.

In the case of structured binding, the Right Thing would have been to not rely on tuple_element at all. They could easily have made

    auto [x,y] = t;

equivalent to

    auto __t = t;
    auto&& x = get<0>(__t);  // with compiler magic to make decltype(x) appear as a non-reference type, of course
    auto&& y = get<1>(__t);

without any reference to tuple_element.  (And notice that the rewrite doesn't explicitly use tuple_size, either. tuple_size is needed only to generate annoying compiler diagnostics in the case that t "has" more than 2 elements. How to bind only the first K ordinates of a tuple and discard the rest is a perennial topic of discussion on this mailing list.)

And that magic will fail for at least one of tuple<int&>, tuple<int&&> and tuple<int>. It can't provide the right answer for all three. Care to explain how that is the Right Thing?

BTW, AFAIK the hardcoded begin/end/get lookup rules in the language are impossible to implement exactly in plain C++. The ADL part can be done by SFINAEing on the well-formedness of an unqualified call where the only candidate found by ordinary lookup is a nonviable candidate. The tricky part is actually the class member lookup, which goes for the member interpretation if there's a member with the right name regardless of its kind, type, or accessibility. I don't know of a way to check that for final and union types.

Arthur O'Dwyer

unread,
May 3, 2017, 6:47:19 AM5/3/17
to ISO C++ Standard - Future Proposals
On Wed, May 3, 2017 at 3:16 AM, T. C. <rs2...@gmail.com> wrote:
On Wednesday, May 3, 2017 at 1:17:34 AM UTC-4, Arthur O'Dwyer wrote:
On Tue, May 2, 2017 at 9:13 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 2, 2017 at 11:16:31 PM UTC-4, Arthur O'Dwyer wrote:

FWIW: Structured binding actually searches for a whole mess of names, including `tuple_size` and `tuple_element` (both of which it looks up only in namespace std). So IMO "structured binding" should be an item on the ever-growing list of "ill-advised places where a customization-point feature was clearly needed but never actually designed," and not on the list of "nice things to copy the design of."

To be honest... how else could it work? Oh sure, you might have found some way to make `tuple_size` be a `constexpr` function or something (though how you pass a parameter of type `E` to allow ADL without actually having to create an object of that type would be an interesting trick). But `tuple_element` has got to resolve to a type. And ADL just doesn't exist for meta-functions.

In the case of structured binding, the Right Thing would have been to not rely on tuple_element at all. They could easily have made

    auto [x,y] = t;

equivalent to

    auto __t = t;
    auto&& x = get<0>(__t);  // with compiler magic to make decltype(x) appear as a non-reference type, of course
    auto&& y = get<1>(__t);

without any reference to tuple_element.  (And notice that the rewrite doesn't explicitly use tuple_size, either. tuple_size is needed only to generate annoying compiler diagnostics in the case that t "has" more than 2 elements. How to bind only the first K ordinates of a tuple and discard the rest is a perennial topic of discussion on this mailing list.)

And that magic will fail for at least one of tuple<int&>, tuple<int&&> and tuple<int>. It can't provide the right answer for all three. Care to explain how that is the Right Thing?

Hm. Today I have learned that "auto& [x,y] = t;" doesn't do what I thought it did.
The "natural" thing for it to have done would have been to turn both x and y into auto& (lvalue reference) types:

    auto& __t = t;
    auto& x = get<0>(__t);  // no compiler magic necessary
    auto& y = get<1>(__t);

But it turns out that it really does something ultra bizarre — namely, it applies the compiler magic to remove the referenceness of decltype(x) even though x does literally have reference semantics.

    std::tuple<int> t1(1);
    auto& [r1] = t1;
    static_assert(std::is_same_v<decltype(r1), int>);  // r1 is not a reference...
    static_assert(!std::is_reference_v<decltype(r1)>);  // 100% not a reference...
    r1 = 2;
    assert(t1 == std::tuple(2));  // ...yet it has reference semantics!


If it had done the thing I had thought it would do (preserve the reference-ness of the declaration), then there wouldn't have been any problem.


Re-reading your objection, I think you might have been talking about something else anyway. I see how the current state of auto [x] versus auto& [x] versus auto&& [x] is a bit screwy; but I don't see the problem you're seeing with std::tuple<int> versus std::tuple<int&> versus std::tuple<int&&>. In those three cases, std::get<0>(__t) will return a value with the appropriate degree of (rvalue-)referenceness, and there won't be any problem as far as I'm aware.

–Arthur

Barry Revzin

unread,
May 3, 2017, 9:12:36 AM5/3/17
to ISO C++ Standard - Future Proposals
That's a fair analogy; but I still can't picture what kind of core language feature would help here.  With lambdas it was pretty obvious what the core feature was; the only bikeshedding was over the spelling of []. With customization points, it's not real clear what we're trying to spell in the first place.  A new call syntax?  A new way of doing name lookup?  A new way of defining functions?


Reaching back into the archives, the original range-based for loop proposal used concepts as customization points: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2778.htm, which would invoke std::Range<_RangeT>::begin(__range) and std::Range<_RangeT>::end(__range)

Or we could just have lifting lambdas and deal with it that way. Whatever happened to P0119, anyway?

Thiago Macieira

unread,
May 3, 2017, 9:43:40 AM5/3/17
to std-pr...@isocpp.org
Em quarta-feira, 3 de maio de 2017, às 03:47:16 PDT, Arthur O'Dwyer escreveu:
> But it turns out that it really does something ultra bizarre — namely, it
> applies the compiler magic to *remove the referenceness of decltype(x)*
> even though x does literally have reference semantics.
>
> std::tuple<int> t1(1);
> auto& [r1] = t1;
> static_assert(std::is_same_v<decltype(r1), int>); // r1 is not a
> reference...
> static_assert(!std::is_reference_v<decltype(r1)>); // 100% not a
> reference...
> r1 = 2;
> assert(t1 == std::tuple(2)); // ...yet it has reference semantics!

It is quite surprising, but you have to understand what a structured binding
is.

auto& [r1] = t1;

Does not create variable called t1 that references t1's first element.

Instead, it creates an unnamed variable that is a reference to t1. That
variable is a structure and it has r1 as its first element.

This is the only way you could do:

struct S {
int v : 31;
int s : 1;
} s;
auto &[value, sign] = s;

This compiles.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Nicol Bolas

unread,
May 3, 2017, 11:38:53 AM5/3/17
to ISO C++ Standard - Future Proposals

I guess I was referring to the wrong proposal. The one I was thinking of made `[]funcname` generate a lambda, rather than the implicit nonsense P0119 was trying to do.

T. C.

unread,
May 3, 2017, 11:48:41 AM5/3/17
to ISO C++ Standard - Future Proposals
You have auto&& x = get<0>(AS_RVALUE(__t)); x is either int& or int&&, depending on __t's type. What kind of "magic" will let decltype(x) get to int, int&, and int&&?

Vicente J. Botet Escriba

unread,
May 3, 2017, 8:54:17 PM5/3/17
to std-pr...@isocpp.org
Le 03/05/2017 à 15:12, Barry Revzin a écrit :
That's a fair analogy; but I still can't picture what kind of core language feature would help here.  With lambdas it was pretty obvious what the core feature was; the only bikeshedding was over the spelling of []. With customization points, it's not real clear what we're trying to spell in the first place.  A new call syntax?  A new way of doing name lookup?  A new way of defining functions?


Reaching back into the archives, the original range-based for loop proposal used concepts as customization points: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2778.htm, which would invoke std::Range<_RangeT>::begin(__range) and std::Range<_RangeT>::end(__range)

Thanks for adding this old link. The calling interface should be even more simple  std2::range::begin(aRange) and std2::range::end(aRange) but not simpler: std2::begin(aRange) and std2::end(aRange).

IMO, the std2 customization mechanism as proposed for the Range TS doesn't scale for the C++ standard library and the approach cannot be used by the users to define his own customization points. The last could be seen as not a problem for the standard C++, but people must solve concrete problems and they copy the C++ library design as it should be the correct one, but they cannot in this case.  See for example the customization mechanism of Boost.Hana and many other generic libraries. If they use ADL as customization point, they can not support C++ standard library types.

We will see more a more concepts very soon. In the same way we associate a member function to a class, we should associate a customization point to a concept and name it. There shouldn't be a confusion of which customization point belongs to what concept. It is not the same product_type::get than sum_type::get, container::size than memory::size. begin/end is not something limited to ranges.  When I say name it I mean that the user must name the concept and the function name when she want to call it and that the customization is done explicitly using this concept name as well. This is not the design used in on the C++ standard up to C++17 and will not change for C++20 with the current direction.

We would need more a more customization points if we continue to do generic programming and not only on the C++ standard library. Having all these customization points in a flat namespace don't scale, the customization points become pseudo keywords. We need to scope them. We have namespaces to manage with scope, and the standard library is not using them (the exceptions of chrono and filesystem are domain namespaces which of course is a very good use of namespaces). I expected to have a std2::ranges namespace in the future STD2 to signal that anything there was talking about customization points of the the Range concept and the algorithms that work with this concept. Instead, IIUC we will have again a flat std2 namespace without structure. I know that this will make the names longer, but we have other C++ features that help us, as namespace aliases. Language that don't have namespaces rely on prefixes to ensure a unique meaning for a customization point.  C++ is more powerful and we should use this power for the standard library.

I don't see the advantage of having implicit mappings that are based on syntactical vraisemblance. Why a class defining begin() and end() function members should become a Range if the functions don't support the Range semantics. While sometimes it is possible to ensure almost all the semantic concerns others it isn't so easy. At the end a syntactical mapping has more liabilities than advantages as most of the short term solutions.

I know that we want backward compatibility and any alternative customization approach needs to address it. This doesn't mean that we should base our future customization strategy on an approach ADL that has a lot of problems, see [N1691].

I agree that we need a language feature to define customization points and a way to customize them. Other languages have already do that with success. C++ is a complex language and we don't want to add anything that make it even more complex. I believe however that we really need to solve this problem at the languages level. IMHO, C++0x addressed in part this problematic but the C++ standard abandoned this direction and people as Bjarne Stroustrup and many others don't want to consider any proposal that has an explicit concept/customization points mapping. This means that people are trying to solve the problem at the library level. I believe that the proposed std2 customization mechanism has his merits on the continuation of this direction. The approach solves already some important problems, but the solution doesn't takes in account the problems addressed in [N1691] and is more complex that the lambda user is able to design. As Bjarne Stroustrup says, "Make simple thing simpler".

Sorry, but I don't have a language proposal. In the mean time I use an alternative customization point mechanism (close to the one of Boost.Hana) that is explicit in the calling side and explicit in the customization side, but the customization solution is a little bit cumbersome. I use it on some of my proposal as Nullable, Factories, Product Type. I'm using the same approach also for other on going proposal I'm working on, as e.g. for Sum Types, Ordinal types, Functors or Monads. These proposals don't propose function object to represent the user interface but this could be considered. IMHO, providing function objects is orthogonal to the customization point approach and the fact that the current STD2 approach requires the use of function objects shouldn't be considered as a goal. As others have noted we have some proposals on overloads sets that would make it very easy to provide a function object associated to an overload set.

The current customization points could be adapted to this explicit approach while preserving backward compatibility, as e.g. for Swappable, Hashable and Range. I don't see these proposals as a final solution and I hope these proposals could inspire some language solution(s) (some kind of explicit namespace, partial template class specialization from unrelated namespaces, traits, ...) or at least a better library solution.

Vicente

[N1691] Explicit Namespaces
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1691.html


Matthew Fioravante

unread,
May 5, 2017, 1:54:16 PM5/5/17
to ISO C++ Standard - Future Proposals
The swap(), begin(), end(), size(), etc... is an embarrassment.

- Its ugly to adding these using declarations.
- Its crazy error prone. Too easy to forget a using declaration.
- These functions cannot be called in single expression contexts like decltype().

Why can't we just make std::swap() do all of the magic dispatching internally? Then the convention is people always call std::swap(), std::begin(), etc..

Nicol Bolas

unread,
May 5, 2017, 2:03:10 PM5/5/17
to ISO C++ Standard - Future Proposals

Because it would be a backwards-incompatible change. It also wouldn't help users create similar interfaces that have similar behavior.

Ville Voutilainen

unread,
May 5, 2017, 2:28:54 PM5/5/17
to ISO C++ Standard - Future Proposals
There have been suggestions to add an adl_swap that does all this
"magic dispatching" internally.

Matthew Fioravante

unread,
May 5, 2017, 2:29:03 PM5/5/17
to ISO C++ Standard - Future Proposals


On Friday, May 5, 2017 at 1:03:10 PM UTC-5, Nicol Bolas wrote:
Because it would be a backwards-incompatible change.

C++ has broken backwards compatibility before. Right now if you call std::swap() on your object, unless its in std you'll get a default move based swap operation. Suppose that was all of the sudden silently changed to actually call your type's swap() operation if it has one. How bad would that be? How much code would it break?

I consider swap() special in the same vein as move and copy. We've added rules to change how those are called/elided in the past knowing it could break code. If you write a swap() its suppose to do swapping, if it does something else you get what you deserve. "Optimizing" std::swap() to call your types custom swap if it has one does not sound like a bad thing to me.

The fact that essential library primitives like swap() and begin() require expert level knowledge to use correctly is a complete and utter failure. Really, its just ****ing stupid. ADL is not the proper tool for this. I'd argue ADL is only really useful for operator overloading. Unless you put swap(), begin(), etc.. to the global :: namespace, ADL will never work. That's a non-starter too as third party libraries wanting to define "customization points" such as begin() certainly cannot just put stuff in the global namespace.

With have virtual functions for runtime dispatch, and this broken crap interface for compile time dispatch. This is a serious bug that needs to be fixed.
 
It also wouldn't help users create similar interfaces that have similar behavior.

That's a secondary concern. If I want to write foo::fooify() customization point in libfoo, I can lookup a tutorial to get all of the dispatching correct. I only need to do this once for all of the thousands if not millions of times my users will just need to say foo::fooify(fooable_thing);. If I'm writing my own customization points, I'm already an expert C++ developer. Furthermore, we could also consider adding helper utilities for this in the standard library, even if they have to be macros.
 

Mathias Gaunard

unread,
May 5, 2017, 2:33:25 PM5/5/17
to std-pr...@isocpp.org
One man's embarassment is another man's good design.
ADL clearly expresses it is an extension point.

Having a function in std:: calling things in your namespace isn't as clean as looking up the function in the associated namespaces. 

Ville Voutilainen

unread,
May 5, 2017, 2:39:05 PM5/5/17
to ISO C++ Standard - Future Proposals
On 5 May 2017 at 21:29, Matthew Fioravante <fmatth...@gmail.com> wrote:
>> Because it would be a backwards-incompatible change.
>
>
> C++ has broken backwards compatibility before. Right now if you call

That's not an open license to break compatibility whenever you feel like it.

> std::swap() on your object, unless its in std you'll get a default move
> based swap operation. Suppose that was all of the sudden silently changed to
> actually call your type's swap() operation if it has one. How bad would that
> be? How much code would it break?

That needs to be explored. Such a change can lead to program breakage
if std::swap starts
calling swaps that weren't meant for swapping, and I have no idea
whether such beasts exist.

> I consider swap() special in the same vein as move and copy. We've added
> rules to change how those are called/elided in the past knowing it could
> break code. If you write a swap() its suppose to do swapping, if it does
> something else you get what you deserve. "Optimizing" std::swap() to call

I don't deserve my working code to stop compiling just because you
have problems with
how std::swap is defined.

> The fact that essential library primitives like swap() and begin() require
> expert level knowledge to use correctly is a complete and utter failure.
> Really, its just ****ing stupid. ADL is not the proper tool for this. I'd

Well, we get very few bug reports about it, and even fewer proposals.
I know, most users
don't report bugs, but there's no indication that this is such a big problem.

Matthew Fioravante

unread,
May 5, 2017, 3:03:36 PM5/5/17
to ISO C++ Standard - Future Proposals


On Friday, May 5, 2017 at 1:39:05 PM UTC-5, Ville Voutilainen wrote:
On 5 May 2017 at 21:29, Matthew Fioravante <fmatth...@gmail.com> wrote:
>> Because it would be a backwards-incompatible change.
>
>
> C++ has broken backwards compatibility before. Right now if you call

That's not an open license to break compatibility whenever you feel like it.

> std::swap() on your object, unless its in std you'll get a default move
> based swap operation. Suppose that was all of the sudden silently changed to
> actually call your type's swap() operation if it has one. How bad would that
> be? How much code would it break?

That needs to be explored. Such a change can lead to program breakage
if std::swap starts
calling swaps that weren't meant for swapping, and I have no idea
whether such beasts exist.

It would have to be a case where someone explicitly calls std::swap() already on their type and the type has a swap() method which is doing something other than swap entirely. That already sounds like an extremely rare (and poorly designed) unicorn to begin with. 

Lets also not forget the standard convention of implementing swap() has been around since the beginning of C++ and has been a standard practice recommended in any C++ tutorial, book, or class for decades. I can't imagine so many people really abuse swap() for something else.

Certainly it does need to be explored. I'm not suggesting we go and break the world without a proper study. The problem is that a lot of times the specter of breaking backwards compatibility even for an extremely rare and improbable edge case shuts out the possibility of even discussing a change entirely from the start.




> I consider swap() special in the same vein as move and copy. We've added
> rules to change how those are called/elided in the past knowing it could
> break code. If you write a swap() its suppose to do swapping, if it does
> something else you get what you deserve. "Optimizing" std::swap() to call

I don't deserve my working code to stop compiling just because you
have problems with
how std::swap is defined.

I certainly doubt any of the code you personally have wrote, or anyone else in this thread would be broken by such a change.

Its not the same as for example fixing min() and max(). That would be a great change, but it almost certainly would break a lot of code and therefore can never be fixed without an STL rewrite.
 

> The fact that essential library primitives like swap() and begin() require
> expert level knowledge to use correctly is a complete and utter failure.
> Really, its just ****ing stupid. ADL is not the proper tool for this. I'd

Well, we get very few bug reports about it, and even fewer proposals.
I know, most users
don't report bugs, but there's no indication that this is such a big problem.

Its an expert level problem. How many expert level users are really aware of all of the caveats? How many of that subset actually file bugs, post in this forum, or somehow otherwise contribute to standardization? How many people just call x.begin() in their code (raising my own hand here) and completely ignore this broken and complicated ADL business?

How many people have needed to do decltype(begin(x)) and then really saw how terrible this API is?

Nicol Bolas

unread,
May 5, 2017, 3:49:31 PM5/5/17
to ISO C++ Standard - Future Proposals
On Friday, May 5, 2017 at 2:33:25 PM UTC-4, Mathias Gaunard wrote:
On 5 May 2017 at 18:54, Matthew Fioravante <fmatth...@gmail.com> wrote:
The swap(), begin(), end(), size(), etc... is an embarrassment.

- Its ugly to adding these using declarations.
- Its crazy error prone. Too easy to forget a using declaration.
- These functions cannot be called in single expression contexts like decltype().

Why can't we just make std::swap() do all of the magic dispatching internally? Then the convention is people always call std::swap(), std::begin(), etc..

One man's embarassment is another man's good design.
ADL clearly expresses it is an extension point.

Except for things that can't be ADL'd, of course (ie: all fundamental types). And for types which use a member function that has the desired behavior, rather than implementing it in two places. Which is why `using std::swap` is used before attempting to invoke ADL swap.

Having to do that for every ADL customization point is the problem.
 
Having a function in std:: calling things in your namespace isn't as clean as looking up the function in the associated namespaces. 

The idea is that `std::swap` would call the `swap` in your namespace via an ADL call. But as a specific overload of the non-ADL `std::swap`  function.

Nicol Bolas

unread,
May 5, 2017, 3:58:13 PM5/5/17
to ISO C++ Standard - Future Proposals
On Friday, May 5, 2017 at 2:29:03 PM UTC-4, Matthew Fioravante wrote:
On Friday, May 5, 2017 at 1:03:10 PM UTC-5, Nicol Bolas wrote:
Because it would be a backwards-incompatible change.

C++ has broken backwards compatibility before. Right now if you call std::swap() on your object, unless its in std you'll get a default move based swap operation. Suppose that was all of the sudden silently changed to actually call your type's swap() operation if it has one. How bad would that be? How much code would it break?

I consider swap() special in the same vein as move and copy. We've added rules to change how those are called/elided in the past knowing it could break code. If you write a swap() its suppose to do swapping, if it does something else you get what you deserve. "Optimizing" std::swap() to call your types custom swap if it has one does not sound like a bad thing to me.

The fact that essential library primitives like swap() and begin() require expert level knowledge to use correctly is a complete and utter failure. Really, its just ****ing stupid. ADL is not the proper tool for this. I'd argue ADL is only really useful for operator overloading. Unless you put swap(), begin(), etc.. to the global :: namespace, ADL will never work. That's a non-starter too as third party libraries wanting to define "customization points" such as begin() certainly cannot just put stuff in the global namespace.

With have virtual functions for runtime dispatch, and this broken crap interface for compile time dispatch. This is a serious bug that needs to be fixed.

I don't disagree on the importance of customization points. I don't disagree with the need to have them work.

That alone however cannot justify doing it in a backwards-incompatible way.

It also wouldn't help users create similar interfaces that have similar behavior.

That's a secondary concern.

I strongly disagree. It's like saying that we need to have ranges in the standard library, but it's a secondary concern to provide tools to help people write standard library compatible ranges. What good is it to have standard idioms if writing idiom-compatible constructs is so complex/arcane that users can not reasonably be expected to define them on their own?

Writing a standard library compatible range should not be hard. Writing a standard library compatible customization point should also not be hard. This should be a primary concern of any solution to this problem. Once we standardize the concept of "customization point", people will and should want to do it in a way that is compatible with the standard library. If we make that simple, then we allow them to do so.

If we make the idiom complex or arcane, all we do is create dozens of headaches down the road.
 
If I want to write foo::fooify() customization point in libfoo, I can lookup a tutorial to get all of the dispatching correct. I only need to do this once for all of the thousands if not millions of times my users will just need to say foo::fooify(fooable_thing);. If I'm writing my own customization points, I'm already an expert C++ developer.

That kind of thinking is what gets us `std::enable_if`. "If I want to do some SFINAE-style stuff, then I'm already an expert C++ developer".

Utter nonsense. By adding appropriate things to the language, we give novices the power to do the things that experts do. We take arcane idioms and bring them to the masses.

When we get concepts, you'll see the number of people using SFINAE increase dramatically. Why? Because it makes it a real part of the language, not some arcane hack. Users have needs that SFINAE could solve, but they don't use it because it's needlessly difficult and over-complicated. The same goes here. Creating a customization points should not be something that requires being an expert C++ developer. Users want to be able to do these things, but as of yet, there is no good, simple solution for it.

Furthermore, we could also consider adding helper utilities for this in the standard library, even if they have to be macros.

Yes, don't bother with a nice, neat language feature. It's much better to do this sort of stuff as a macro.

Matthew Fioravante

unread,
May 5, 2017, 4:40:43 PM5/5/17
to ISO C++ Standard - Future Proposals


On Friday, May 5, 2017 at 2:58:13 PM UTC-5, Nicol Bolas wrote:
On Friday, May 5, 2017 at 2:29:03 PM UTC-4, Matthew Fioravante wrote:
On Friday, May 5, 2017 at 1:03:10 PM UTC-5, Nicol Bolas wrote:
Because it would be a backwards-incompatible change.

C++ has broken backwards compatibility before. Right now if you call std::swap() on your object, unless its in std you'll get a default move based swap operation. Suppose that was all of the sudden silently changed to actually call your type's swap() operation if it has one. How bad would that be? How much code would it break?

I consider swap() special in the same vein as move and copy. We've added rules to change how those are called/elided in the past knowing it could break code. If you write a swap() its suppose to do swapping, if it does something else you get what you deserve. "Optimizing" std::swap() to call your types custom swap if it has one does not sound like a bad thing to me.

The fact that essential library primitives like swap() and begin() require expert level knowledge to use correctly is a complete and utter failure. Really, its just ****ing stupid. ADL is not the proper tool for this. I'd argue ADL is only really useful for operator overloading. Unless you put swap(), begin(), etc.. to the global :: namespace, ADL will never work. That's a non-starter too as third party libraries wanting to define "customization points" such as begin() certainly cannot just put stuff in the global namespace.

With have virtual functions for runtime dispatch, and this broken crap interface for compile time dispatch. This is a serious bug that needs to be fixed.

I don't disagree on the importance of customization points. I don't disagree with the need to have them work.

That alone however cannot justify doing it in a backwards-incompatible way.

It also wouldn't help users create similar interfaces that have similar behavior.

That's a secondary concern.

I strongly disagree. It's like saying that we need to have ranges in the standard library, but it's a secondary concern to provide tools to help people write standard library compatible ranges. What good is it to have standard idioms if writing idiom-compatible constructs is so complex/arcane that users can not reasonably be expected to define them on their own?

Writing a standard library compatible range should not be hard. Writing a standard library compatible customization point should also not be hard. This should be a primary concern of any solution to this problem. Once we standardize the concept of "customization point", people will and should want to do it in a way that is compatible with the standard library. If we make that simple, then we allow them to do so.

If we make the idiom complex or arcane, all we do is create dozens of headaches down the road.

So then add the tools to help create these things. I never said we shouldn't. However when making engineering trade-offs in the interface, its better to design something optimized for the common case (calling a customization point) over the rare case (writing the default dispatch driver for a customization point library).
 
 
If I want to write foo::fooify() customization point in libfoo, I can lookup a tutorial to get all of the dispatching correct. I only need to do this once for all of the thousands if not millions of times my users will just need to say foo::fooify(fooable_thing);. If I'm writing my own customization points, I'm already an expert C++ developer.

That kind of thinking is what gets us `std::enable_if`. "If I want to do some SFINAE-style stuff, then I'm already an expert C++ developer".

I don't agree with this comparison. Hacking overload resolution with enable_if is something we do way more often than creating new customization points.

The use case here is not "I want to do SFINAE stuff" (expert only implementation specific gibberish), its "I want write a math function which only operates on any kind of `real number' type" (basic core idea).

Before we go ahead and add language features, ugly less invasive things like this are a good start to get experience. Everybody hates enable_if for good reason but before concepts that's all we had to solve a real need. The standard library 10 years from now is not going to be any worse off for the existence of an old enable_if template now collecting dust unused in the new conceptified regime.



Utter nonsense. By adding appropriate things to the language, we give novices the power to do the things that experts do. We take arcane idioms and bring them to the masses.

When we get concepts, you'll see the number of people using SFINAE increase dramatically. Why? Because it makes it a real part of the language, not some arcane hack. Users have needs that SFINAE could solve, but they don't use it because it's needlessly difficult and over-complicated. The same goes here. Creating a customization points should not be something that requires being an expert C++ developer. Users want to be able to do these things, but as of yet, there is no good, simple solution for it.

Furthermore, we could also consider adding helper utilities for this in the standard library, even if they have to be macros.

Yes, don't bother with a nice, neat language feature. It's much better to do this sort of stuff as a macro.

Ok so invent a new language feature then. I'd be plenty happy with that too. I don't think you can do this with templates as they are currently which is why a macro was suggested. A language feature would need to be more general purpose. I'm not sure it would ever fly to create a core language feature solely for the purpose of writing customization points.

I'm not at all married to using std::swap() to solve the problem. If someone has a better solution I'll be the first one to jump on board. Whatever the solution is, this using std::swap; swap(); nonsense has got to go.

It also needs to be solved not just for swap(), but for all customization points including begin(), end(), cbegin(), cend(), rbegin(), rend(), size(), data(), etc.. As an added bonus, this should also mean we can all stop doing the brainless and bug friendly work of writing cbegin(), cend(), rbegin(), rend(), rbegin() const, rend() const, crbegin(), crend() for all of our custom container classes. Instead just relying on the default adapters generated by the std:: default versions adapting begin(), begin() const, end(), and end() const.



Lets not also forget we have concepts on the way. Once concepts are out in the wild this ADL problem is going to become a much bigger issue fast. Its was one of the main use cases driving unified function call syntax. Do we want to wait until the bug reports and complaints after concepts or should we not try to attack this issue now?

If I want to make an Iterable concept, I'd like to just say that it begin(x) and end(x) are valid expressions which return Iterator concepts. Why the hell do I need to deal with these using declaration gymnastics? How do I carefully add them without polluting the entire scope? I still haven't review the concepts proposal in detail. Can I even put a using declaration inside a concept expression?

For homework, try to implement the following from scratch. Do not cheat with type_traits, concepts, or any other std:: machinery that already solves the problem. Do not pollute the enclosing scope with using declarations.

* Given a type T, write a noexcept() expression which is true if the swap() expression for an object of type T would be noexcept.
* Given a type T, write a decltype() expression to get the type resulting from a begin() expression called on an object of type T.

Howard Hinnant

unread,
May 5, 2017, 9:48:38 PM5/5/17
to std-pr...@isocpp.org
On May 5, 2017, at 2:29 PM, Matthew Fioravante <fmatth...@gmail.com> wrote:
>
> Right now if you call std::swap() on your object, unless its in std you'll get a default move based swap operation. Suppose that was all of the sudden silently changed to actually call your type's swap() operation if it has one. How bad would that be? How much code would it break?

I’ve seen this code in the wild:

struct A
{
void swap(A& a)
{
std::swap(*this, a);
}
};

I’m not claiming it is good code. Just that it exists. How frequent, I’m not sure. But more than none.

If we have std::swap call A’s swap, this A silently gets infinite recursion.

Howard

signature.asc

Arthur O'Dwyer

unread,
May 5, 2017, 11:07:37 PM5/5/17
to ISO C++ Standard - Future Proposals, Marc Mutz
On Wed, May 3, 2017 at 2:14 AM, Marc Mutz <marc...@kdab.com> wrote:

While it's true that std::swap is the most-prominent example, it's also one
which really is trivial in many ways. [...] Much more

This is a very thoughtful description of a real problem that I had not been considering, and I'm sorry it seems to have gotten minorly buried in this thread.

I like your examples of std::sin() and std::reverse() as "customization points."  You're correct that I would not propose trying to use Niebler-style "customization point" lambdas for those.  One takeaway point here is that the set of possible customization points in C++ is an open set, not a closed set.

The std::reverse example is particularly interesting to me. Consider this implementation of std::reverse, and then this implementation of std::rotate:

    template<class It>
    void reverse(It first, It last)
    {
        while (first != last) {
            --last;
            if (first == last) break;
            using std::swap;
            swap(*first, *last);
            ++first;
        }
    }

    template<class It>
    It rotate(It a, It mid, It b)
    {
        auto result = a + (b - mid);
        std::reverse(a, b);
        std::reverse(a, result);
        std::reverse(result, b);
        return result;
    }

The implementation of std::reverse goes out of its way to respect the "customization-point-ness" of swap, but the implementation of std::rotate does not go out of its way to respect the "customization-point-ness" of reverse.  So, if your particular data type has a speedy reverse, std::rotate will be inefficient.

Now, the Standard might reasonably say that this state of affairs is fine. std::rotate isn't supposed to delegate to your speedy reverse; it's not even guaranteed to call std::reverse (and in fact libstdc++ calls something named __reverse, and libc++ doesn't use swap at all in most cases).  It's just supposed to rotate the given range by some unspecified means, and if you want a rotate function that will use your fast reverse, you'll just have to write it yourself.  This argument is fine, as far as it goes.

But the Standard Library isn't all libraries. IMHO it would be reasonable for some library designer out there to say, "My numerics library is generic enough to work with all numeric datatypes; if it ever calls sin(), or log(), or max(), or sort(), or any function, it'll do it in a way that you can customize."  And then that designer will need a solution for how to call any arbitrary function while respecting ADL.  C++03 has a solution for that, but it's clunky, and as Matthew Fioravante pointed out, the C++03 solution doesn't play well with decltype(swap(a,b)) or noexcept(noexcept(swap(a,b))).

That is, one might say that the chief failing of the C++03 solution "using std::swap; swap(a,b)" is that it has failed to keep up with the syntactic changes in C++11.  If it weren't for that, it would still be okay, if never excellent.

my $.02,
–Arthur

Matthew Fioravante

unread,
May 6, 2017, 2:52:20 AM5/6/17
to ISO C++ Standard - Future Proposals, marc...@kdab.com
One other deficiency of the using std::swap; swap(); idiom is that you lose namespace scoping when invoking your customization points.


If I had liba::foo, and libb::foo separate customization points, I cannot use them together in the same block of code without some very clumsy scoping. Error prone and terribly ugly more complex code.

When I actually call it, I just say swap(x, y). I have an implicit dependency on which swap was pulled in by a preceding using declaration. Most of the time its on the preceding line, so its obvious. However if you have a bunch of calls in the code, you'll want to organize the using declarations and now thats one more thing to track when reading the code.


One compromise would be something like using namespace std::literals;

You might say something like:

using namespace std::custom;

Other libraries like libfoo can support using namespace foo::custom;

Then you automatically get swap(), and the others in your global namespace. This makes ADL work at the expense of losing the capability of namespace scoping in invoking customization point names. Fundamentally, this is the only way ADL can work. You also still don't get some of the other examples like reverse() or sin(). It all comes at a cost of this line of boilerplate at the top of your source files and the decision that its always ok to have these std symbols in your namespace.

I don't believe ADL at the callsite is up to the task. We either wrap its deficiencies away with a standard name like std::swap, adl::swap, or something else. Or somehow design yet another set of core language function calling rules to somehow handle this case. The second option sounds terrifying, especially with all the complicated rules the language already has.

Unified call syntax was shot down. Right or wrong, it actually didn't solve this problem either. UCS lets me not have to worry about saying swap(x, y) vs x.swap(y). It still doesn't give me an automatic fallback to std::swap() without a using declaration.

std::swap might be particularly scary. Howard Hinnant produced a great counter example.

Another way out is to accept one of these swap operator proposals. Then we can do it right with the swap operator from scratch. We completely sidestep the swap() backwards compatibility issue and the ADL customization point problem now only is limited to begin(), data(), size(), etc.. These functions are an order of magnitude less dangerous than swap when it comes to possibly breaking backwards compatibility.

Vicente J. Botet Escriba

unread,
May 6, 2017, 9:14:28 AM5/6/17
to std-pr...@isocpp.org
Would you agree to call them giving the specific concepts we are
customizing as e.g.

std::swappable::swap
std::range::begin
std::range::end
....

?

The new interface should, of course, ensure that it calls your
customization point.

This avoids the cyclic issue HH reported.

Vicente

Vicente J. Botet Escriba

unread,
May 6, 2017, 9:41:57 AM5/6/17
to std-pr...@isocpp.org
Le 03/05/2017 à 11:14, Marc Mutz a écrit :
> On Wednesday 03 May 2017 10:13:49 ol...@join.cc wrote:
>> Op woensdag 3 mei 2017 04:08:12 UTC+2 schreef Arthur O'Dwyer:
>>> IMHO, the most elegant solution (basically Eric Niebler's proposal) would
>>> involve MASSIVE breakage of old code and is thus suited only for "std2".
>> Wouldn't it work as std::swap2?
> Sorry for highjacking Olaf's reply. This is not about Olaf's answer.
>
> While it's true that std::swap is the most-prominent example, it's also one
> which really is trivial in many ways. Few are the types which these days of
> move semantics _need_ a custom swap(). Unless you still need to support C++98
> compilers, it's merely a questionable optimisation technique (the best you can
> do it member-wise swapping, which apart from a little less memory used, is
> hardly orders of magnitude faster than what the triple-move default
> implementation does).
>
> No, forget swap. The Swap Problem is not (just) about swap. Much more
> interesting than swap() are those functions which are API enablers:
Agreed.
> A class template representing angles _needs_ a sin() overload, and up pops the
> question how to provide it: via ADL, even though most people will likely not
> write
>
> using std::sin;
> return sin(a);
>
> ? Or as a specialisation of std::sin() (doen't work) or as an overload of
> std::sin() (not allowed). Example: https://codereview.qt-project.org/191717
> So, yeah, via ADL, and thus continues the embarrassment...
>
>
> A non-std container should provide a version of LF v2's erase_if(). Even
> though erase_if is only overloaded on types in namespace std, since it's
> currently in namespace std::experimental, you still can't say
>
> erase_if(c, p);
>
> ie. rely on ADL, but need to have another using declaration:
>
> using std::experimental::erase_if;
> erase_if(c, p);
Would you accept to call

std::container::erase_if(c,p);

or something like that?
> This stuff usually gets a laugh from non-C++ experts already.
> But note how this using declaration will fail to compile when LF v2 is not
> supported, requiring adding preprocessor magic around it:
>
> #if defined(_cpp_lib_dunno_exact_name) && \
> __cpp_lib_dunno_exact_name >= 201411
> using std::experimental::erase_if;
> #endif
> erase_if(c, p);
This is another issue.
> Ok, all of this will be solved when erase_if moves into namespace std proper,
> making this a bad example.
>
> Let's look at STL algorithms:
>
> A wonky container like QList can actually implement reversing as an out-of-
> line function: https://codereview.qt-project.org/144071 It's not far-fetched
> to want to specialise std::reverse() to call the member function, which would
> require overloading std::reverse(). Or providing an ADL version, which,
> however, requires every use of std::reverse() to say
>
> using std::reverse;
> reverse(b, e);
AFAIK, reverse is not a customization point. We will need a proposal for
that ;-)
> Here, we finally reached a case where no-one in the world writes code like
> this.
>
> As you can see, the issue is not at all restricted to swap(), begin(), size(),
> data() and the very few other customisation points that are being discussed.
> Do you seriously want to make a std::sin2, std::reverse2, ... for every single
> function in namespace std?
I would prefer to associate them to the concept the customization is
associated to.
>
> So I wonder: What's the rationale for not allowing adding overloads under the
> same conditions as allowing partial specialisations of class templates? If a
> user messes up, how is it a worse mess with overloads than with class template
> specialisations? What's so scary about it that you'd rather discuss _new
> language features_ to enable customisation than to allow it by what C++
> provides for ages, and even novices quickly grasp: overloads?
>
From Range TS customization point N4381 we have athe following goals

"The goals of customization point design are as follows (for some
hypothetical future customization point cust):

Code that calls cust either qualified as std::cust(a); or
unqualified as using std::cust; cust(a); should behave identically. In
particular, it should find any user-defined overloads in the argument’s
associated namespace(s).
Code that calls cust as using std::cust; cust(a); should not bypass
any constraints defined on std::cust.
Calls to the customization point should be optimally efficient by
any reasonably modern compiler.
The solution should not introduce any potential violations of the
one-definition rule or excessive executable size bloat."

I don't agree with all this. I don't believe that calling cust

using std::cust;
cust(a);

is a must work, even if this is the way we do now.


I will replace the goals by

Code that calls std::a_concept::cust(a); should end by calling the
user-defined customized point.
User can not call cust by ADL, that is using std::a_concept::cust;
cust(a) should be incorrect.
Calls to the customization point should be optimally efficient by
any reasonably modern compiler.
The solution should not introduce any potential violations of the
one-definition rule or excessive executable size bloat.


Of course we cannot prevent the user to call cust(a) directly, except if
we add some feature that prevents that ([N1691] Explicit Namespaces).


So to answer to your comment.we want a single point that is able to
ensure some constraints (Method Pattern) and call to the specific
customized part. Adding overload in std will not satisfy these goal. I
believe the goals are desirable.


Vicente


Vicente J. Botet Escriba

unread,
May 6, 2017, 9:58:08 AM5/6/17
to std-pr...@isocpp.org
Le 05/05/2017 à 22:40, Matthew Fioravante a écrit :


On Friday, May 5, 2017 at 2:58:13 PM UTC-5, Nicol Bolas wrote:
On Friday, May 5, 2017 at 2:29:03 PM UTC-4, Matthew Fioravante wrote:
On Friday, May 5, 2017 at 1:03:10 PM UTC-5, Nicol Bolas wrote:
Because it would be a backwards-incompatible change.

C++ has broken backwards compatibility before. Right now if you call std::swap() on your object, unless its in std you'll get a default move based swap operation. Suppose that was all of the sudden silently changed to actually call your type's swap() operation if it has one. How bad would that be? How much code would it break?

I consider swap() special in the same vein as move and copy. We've added rules to change how those are called/elided in the past knowing it could break code. If you write a swap() its suppose to do swapping, if it does something else you get what you deserve. "Optimizing" std::swap() to call your types custom swap if it has one does not sound like a bad thing to me.

The fact that essential library primitives like swap() and begin() require expert level knowledge to use correctly is a complete and utter failure. Really, its just ****ing stupid. ADL is not the proper tool for this. I'd argue ADL is only really useful for operator overloading. Unless you put swap(), begin(), etc.. to the global :: namespace, ADL will never work. That's a non-starter too as third party libraries wanting to define "customization points" such as begin() certainly cannot just put stuff in the global namespace.

With have virtual functions for runtime dispatch, and this broken crap interface for compile time dispatch. This is a serious bug that needs to be fixed.

I don't disagree on the importance of customization points. I don't disagree with the need to have them work.

That alone however cannot justify doing it in a backwards-incompatible way.

It also wouldn't help users create similar interfaces that have similar behavior.

That's a secondary concern.

I strongly disagree. It's like saying that we need to have ranges in the standard library, but it's a secondary concern to provide tools to help people write standard library compatible ranges. What good is it to have standard idioms if writing idiom-compatible constructs is so complex/arcane that users can not reasonably be expected to define them on their own?

Writing a standard library compatible range should not be hard. Writing a standard library compatible customization point should also not be hard. This should be a primary concern of any solution to this problem. Once we standardize the concept of "customization point", people will and should want to do it in a way that is compatible with the standard library. If we make that simple, then we allow them to do so.

If we make the idiom complex or arcane, all we do is create dozens of headaches down the road.

So then add the tools to help create these things. I never said we shouldn't. However when making engineering trade-offs in the interface, its better to design something optimized for the common case (calling a customization point) over the rare case (writing the default dispatch driver for a customization point library).
I agree that calling the customization must be as simple as possible. However customizing shouldn't be too complex neither. These are simple things.

 
 
If I want to write foo::fooify() customization point in libfoo, I can lookup a tutorial to get all of the dispatching correct. I only need to do this once for all of the thousands if not millions of times my users will just need to say foo::fooify(fooable_thing);. If I'm writing my own customization points, I'm already an expert C++ developer.

That kind of thinking is what gets us `std::enable_if`. "If I want to do some SFINAE-style stuff, then I'm already an expert C++ developer".

I don't agree with this comparison. Hacking overload resolution with enable_if is something we do way more often than creating new customization points.

The use case here is not "I want to do SFINAE stuff" (expert only implementation specific gibberish), its "I want write a math function which only operates on any kind of `real number' type" (basic core idea).
Right. We want to associate a function to a RealNumber type. We need the minimal set of functions that define this RealNumber concept. I call those customization points. We could as well define algorithms that have a default implementation that could become customization points as the user can do better. So we need customization points with default behavior. Maybe we have a very good implementation for some more constrained RealNumber, and I would expect we are able to customize once for all to all the types that satisfy the additional constraints.


Before we go ahead and add language features, ugly less invasive things like this are a good start to get experience. Everybody hates enable_if for good reason but before concepts that's all we had to solve a real need. The standard library 10 years from now is not going to be any worse off for the existence of an old enable_if template now collecting dust unused in the new conceptified regime.



Utter nonsense. By adding appropriate things to the language, we give novices the power to do the things that experts do. We take arcane idioms and bring them to the masses.

When we get concepts, you'll see the number of people using SFINAE increase dramatically. Why? Because it makes it a real part of the language, not some arcane hack. Users have needs that SFINAE could solve, but they don't use it because it's needlessly difficult and over-complicated. The same goes here. Creating a customization points should not be something that requires being an expert C++ developer. Users want to be able to do these things, but as of yet, there is no good, simple solution for it.

Furthermore, we could also consider adding helper utilities for this in the standard library, even if they have to be macros.

Yes, don't bother with a nice, neat language feature. It's much better to do this sort of stuff as a macro.

Ok so invent a new language feature then. I'd be plenty happy with that too.
It seems it is no so simple :)

I don't think you can do this with templates as they are currently which is why a macro was suggested. A language feature would need to be more general purpose. I'm not sure it would ever fly to create a core language feature solely for the purpose of writing customization points.

I'm not at all married to using std::swap() to solve the problem. If someone has a better solution I'll be the first one to jump on board. Whatever the solution is, this using std::swap; swap(); nonsense has got to go.
STD2 with Range TS, goes already in this direction. std2::swap will do what you want. We are lucky we will use a new namespace.


It also needs to be solved not just for swap(), but for all customization points including begin(), end(), cbegin(), cend(), rbegin(), rend(), size(), data(), etc..
This is also the case for these functions.

As an added bonus, this should also mean we can all stop doing the brainless and bug friendly work of writing cbegin(), cend(), rbegin(), rend(), rbegin() const, rend() const, crbegin(), crend() for all of our custom container classes. Instead just relying on the default adapters generated by the std:: default versions adapting begin(), begin() const, end(), and end() const.

Yes, see above about defaulted customization points.



Lets not also forget we have concepts on the way. Once concepts are out in the wild this ADL problem is going to become a much bigger issue fast. Its was one of the main use cases driving unified function call syntax. Do we want to wait until the bug reports and complaints after concepts or should we not try to attack this issue now?
I believe the problem should be solved asap. The main problem will be on finding a consensual solution.


If I want to make an Iterable concept, I'd like to just say that it begin(x) and end(x) are valid expressions which return Iterator concepts. Why the hell do I need to deal with these using declaration gymnastics? How do I carefully add them without polluting the entire scope? I still haven't review the concepts proposal in detail. Can I even put a using declaration inside a concept expression?
This makes customization points pseudo-keywords. We have namespace to localize names. Why not start using them?

Vicente

Marc Mutz

unread,
May 11, 2017, 5:41:20 AM5/11/17
to std-pr...@isocpp.org
On Saturday 06 May 2017 15:41:53 Vicente J. Botet Escriba wrote:
> So to answer to your comment.we want a single point that is able to
> ensure some constraints (Method Pattern) and call to the specific
> customized part. Adding overload in std will not satisfy these goal. I
> believe the goals are desirable.

You are already allowed to "customise" a) every std class template (by full or
partial specialisation, which needs to mention a user type), b) every std
function template (by full specialisation, which needs to mention a user
type). And there's no central dispatch to enforce constraints there, either
(and $DEITY forbid someone starts to specialise std::allocator, or
std::is_pod, ...).

I can already specialise std::reverse for any concrete instantiation of QList,
by full specialisation. The embarrassment is that I can't do this for the
QList template itself (ie. all instantations of it).

So, from my POV of developer-in-the-trenches all that talk about customisation
points, associcated with concepts, is just hot air. The issue is why overloads
of std function (s|templates), which are the moral equivalent of partial class
template specialisation, are not allowed, and why some complex years-in-the-
future mythical language feature is needed, to artifically constrain something
that's already unconstrained.

Marc Mutz

unread,
May 11, 2017, 5:58:58 AM5/11/17
to std-pr...@isocpp.org
On Friday 05 May 2017 21:49:30 Nicol Bolas wrote:
> > Having a function in std:: calling things in your namespace isn't as
> > clean as looking up the function in the associated namespaces.
>
> The idea is that `std::swap` would call the `swap` in your namespace via
> an ADL call. But as a specific overload of the non-ADL
> `std::swap` function.

The idea is to allow users to overload std::swap, in namespace std (where
'swap' stands for every std function), provided the arguments mention user-
defined types.

As I wrote below in this thread, every std template is already a customisation
point. And used as such ever since Effective C++ came out, and that means
probably even earlier).

The embarrassement is not that I cannot specialise std::swap for my own types
(I can), the embarrassment is that I can't do it for my own class _templates_
(because of no partial function specialisation, this would need to overload
and this is forbidden).

I'd be fine if C++20 allowed to overload functions in namespace std, since as
soon as that is voted in, the present UB effectively becomes allowed (since no
implementation checks for it, anyway), and I can move my ADL swaps into
namespace std. This will not break users of the using-swap idiom. It will
break users that use unqualified swap() without using std::swap, but those
deserve to be broken.

Nicol Bolas

unread,
May 11, 2017, 12:06:41 PM5/11/17
to ISO C++ Standard - Future Proposals
On Thursday, May 11, 2017 at 5:41:20 AM UTC-4, Marc Mutz wrote:
On Saturday 06 May 2017 15:41:53 Vicente J. Botet Escriba wrote:
> So to answer to your comment.we want a single point that is able to
> ensure some constraints (Method Pattern) and call to the specific
> customized part. Adding overload in std will not satisfy these goal. I
> believe the goals are desirable.

You are already allowed to "customise" a) every std class template (by full or
partial specialisation, which needs to mention a user type), b) every std
function template (by full specialisation, which needs to mention a user
type). And there's no central dispatch to enforce constraints there, either
(and $DEITY forbid someone starts to specialise std::allocator, or
std::is_pod, ...).

Not everyone agrees that specialization should be allowed for function templates either. P0551 (PDF) makes the case that this should be removed.

I can already specialise std::reverse for any concrete instantiation of QList,
by full specialisation. The embarrassment is that I can't do this for the
QList template itself (ie. all instantations of it).

So, from my POV of developer-in-the-trenches all that talk about customisation
points, associcated with concepts, is just hot air.

But that's not the general POV of "developer-in-the-trenches". That's the POV of your development habits. Other developers don't necessarily do that.

The issue is why overloads
of std function (s|templates), which are the moral equivalent of partial class
template specialisation, are not allowed, and why some complex years-in-the-
future mythical language feature is needed, to artifically constrain something
that's already unconstrained.

But you have to write these overloads. The whole point of customization points is that it standardizes the interface without you being forced to do something special. The `swap` customization point allows you to write member `swap` or ADL `swap`, whichever is most appropriate for your type. Your way requires writing member `swap` and overloading `std::swap`.

How is that an improvement? Oh sure, its easier on the caller, but it's harder on the writer.

With a proper language feature, we can make it easier on everyone.

Vicente J. Botet Escriba

unread,
May 11, 2017, 3:37:04 PM5/11/17
to std-pr...@isocpp.org
Le 11/05/2017 à 11:40, Marc Mutz a écrit :
> On Saturday 06 May 2017 15:41:53 Vicente J. Botet Escriba wrote:
>> So to answer to your comment.we want a single point that is able to
>> ensure some constraints (Method Pattern) and call to the specific
>> customized part. Adding overload in std will not satisfy these goal. I
>> believe the goals are desirable.
> You are already allowed to "customise" a) every std class template (by full or
> partial specialisation, which needs to mention a user type), b) every std
> function template (by full specialisation, which needs to mention a user
> type).
I was aware that we can specialize some classes, but not all, and even
less the full specialize a std function. Could you point me to the
references? Nevertheless this doesn't solve the customization points we
are talking of.
> And there's no central dispatch to enforce constraints there, either
> (and $DEITY forbid someone starts to specialise std::allocator, or
> std::is_pod, ...).
>
> I can already specialise std::reverse for any concrete instantiation of QList,
> by full specialisation. The embarrassment is that I can't do this for the
> QList template itself (ie. all instantations of it).
I guess you are not happy then.
>
> So, from my POV of developer-in-the-trenches all that talk about customisation
> points, associcated with concepts, is just hot air.
I will not talk about what developers think. I consider my self a
library developer and I don't agree with you here.
In generic programming we need customization points in one way or another.

> The issue is why overloads
> of std function (s|templates), which are the moral equivalent of partial class
> template specialisation, are not allowed, and why some complex years-in-the-
> future mythical language feature is needed, to artifically constrain something
> that's already unconstrained.
>
I believe someone has already responded to this question, but maybe it
was in another thread.

Vicente

Vicente J. Botet Escriba

unread,
May 11, 2017, 4:02:40 PM5/11/17
to std-pr...@isocpp.org
Le 11/05/2017 à 18:06, Nicol Bolas a écrit :
On Thursday, May 11, 2017 at 5:41:20 AM UTC-4, Marc Mutz wrote:
On Saturday 06 May 2017 15:41:53 Vicente J. Botet Escriba wrote:
> So to answer to your comment.we want a single point that is able to
> ensure some constraints (Method Pattern) and call to the specific
> customized part. Adding overload in std will not satisfy these goal. I
> believe the goals are desirable.

You are already allowed to "customise" a) every std class template (by full or
partial specialisation, which needs to mention a user type), b) every std
function template (by full specialisation, which needs to mention a user
type). And there's no central dispatch to enforce constraints there, either
(and $DEITY forbid someone starts to specialise std::allocator, or
std::is_pod, ...).

Not everyone agrees that specialization should be allowed for function templates either. P0551 (PDF) makes the case that this should be removed.
Thanks for pointing out this link. I missed it completely.

Vicente


Reply all
Reply to author
Forward
0 new messages