Non-member function call, subscripting and dereferencing operators

283 views
Skip to first unread message

jmo...@aldebaran.com

unread,
Jun 23, 2017, 1:03:38 PM6/23/17
to ISO C++ Standard - Future Proposals
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Thank you in advance for your reviews and comments.
proposal_non_member_operators.txt

Nicol Bolas

unread,
Jun 23, 2017, 1:50:31 PM6/23/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

OK, first question: what's the syntax for preventing people from overriding these things on my types?

These operators are pretty intimately connected to the very interfaces by which we use to interact with objects of those types. It fundamentally means something about a type that it has a function call operator on it, for example. That type is a "functor".

If I don't want my type to be a functor, that is my prerogative. So there needs to be a way to say that I don't want someone to force my type to have an interface that I didn't give it.

Indeed, the ability to define `operator()` from outside a class effectively means that you can insert any interface into any class type you want. You'd just add an `operator()` overload, using a tag type to differentiate which pseudo-member function you want to call.

It seems to me that what you're trying to do would be best done with specialization functions, not through direct interactions with a type.

The main motivation for this proposal is consistency.

That's not really good enough of a reason, as the inconsistency actually has a purpose. As explained above.

It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Specialization functions would be the preferred way to handle that.

Hyman Rosen

unread,
Jun 23, 2017, 3:10:38 PM6/23/17
to std-pr...@isocpp.org, jmo...@aldebaran.com
On Fri, Jun 23, 2017 at 1:50 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

OK, first question: what's the syntax for preventing people from overriding these things on my types?

Wanting to prevent people from doing things is a bad idea, because the people who want to do things are living in the future of the people who want to prevent them from doing things, and the people in the future have better information about what they need than people in the past.

In any case, having those functions defined as non-members is no different from having any other functions or operators that take such objects as parameters.  I don't see why operator[] should be treated differently than operator+.

Jonathan Müller

unread,
Jun 23, 2017, 3:38:54 PM6/23/17
to std-pr...@isocpp.org
On 23.06.2017 19:50, Nicol Bolas wrote:
> On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
>
> Hello,
>
> This is a proposal to allow operator(), operator[] and operator-> to
> be defined as non-member functions.
> I do not know if this has already been proposed and potential
> arguments against it, so please let me know.
>
>
> OK, first question: what's the syntax for /preventing people/ from
> overriding these things on my types?
>

What's the syntax for preventing people from overloading operator+?
What's the syntax for preventing people from overloading the stream
insertion operator?
What's the syntax for preventing people from creating a free function
that does additional operation?
Granted, syntax is slightly different, but only that, plus also:
What's the syntax for preventing people from specializing some traits
for your type?

`operator()`, `operator[]` and `operator->` aren't that special, you can
do enough harm already.

Barry Revzin

unread,
Jun 23, 2017, 3:59:24 PM6/23/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com

On Friday, June 23, 2017 at 12:03:38 PM UTC-5, jmo...@aldebaran.com wrote:
The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

The examples for non-member operator[] and operator() seem pretty uncompelling. The first one seems like a bad algorithm (why not just pass in a predicate instead of enforcing indexing? It'd probably be less typing overall), the second one doesn't even directly use your operator() anywhere - it's buried... somewhere. I think these could use much stronger examples of cases where the best solution would be to add the overload, but we can't, so we're left with some subpar solution. And provide before/after examples demonstrating the improvement. Otherwise, right now it's kind of... meh. 

For operator->(), that one is fundamentally different. Either the class is designed to be a proxy pointer/iterator... or not. What would it mean to add operator->() to some other class? Do you have an example for wanting to do this? 

Nicol Bolas

unread,
Jun 23, 2017, 5:17:59 PM6/23/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com

First, I contest the idea that `operator()` is no different from the others. You might argue that with `->` and `[]`, but not function call. The reason being that `operator()` has no parameter restrictions associated with it; you can overload it with any number of parameters of any type. `operator->` takes no parameters, and `operator[]` takes exactly one parameter. So the damage you can cause by external overloads is minimal.

But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

Second, my principle objection is that these operations are fundamentally part of the type. If a type is not a pointer-like type, then it has absolutely no business overloading `operator->`. If a type is not an array-like type, then it has no business overloading `operator[]`. And if a type is not a function-like type, then it has no business overloading `operator()`. And these are fundamental properties of the type; they're not something you can just graft onto any old type you like.

We allow overloading `operator+` from outside of a type because we need to. That allows you to add two different types. The same goes for most of the rest of the out-of-class overloads; it permits mixing. We allow this for prefix and postfix unary operators because... well, I have no idea why, but I wouldn't have permitted it. Of course, the saving grace there is that it really doesn't matter, because there can only be one pre/postfix overload for each type, for each unary operator.

Hyman Rosen

unread,
Jun 23, 2017, 6:51:43 PM6/23/17
to std-pr...@isocpp.org, jmo...@aldebaran.com
On Fri, Jun 23, 2017 at 5:17 PM, Nicol Bolas <jmck...@gmail.com> wrote:
So the damage you can cause

What makes it "damage"?
 
But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

And that could lead to dancing?  What exactly is the problem here?  It makes that much of a difference to deny saying Object(a, b, c) and forcing people to say Apply(Object, a, b, c)?

As is, a type that has a member function template makes even its private members accessible to everyone, so privacy is dead anyway:

    class X { int m; public: template <class T> void f() const { } };
    class H { };
    static int private_m;
    template <> void X::f<H>() const { private_m = m; }
    int read_m(const X &x) { x.f<H>(); return private_m; }

Nicol Bolas

unread,
Jun 23, 2017, 7:40:35 PM6/23/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Friday, June 23, 2017 at 6:51:43 PM UTC-4, Hyman Rosen wrote:
On Fri, Jun 23, 2017 at 5:17 PM, Nicol Bolas <jmck...@gmail.com> wrote:
So the damage you can cause

What makes it "damage"?

You have taken a type which was not a functor and made it a functor. You have taken a type that was not array-like or pointer-like and made it array/pointer-like.

If those are not the purpose of those types, then you have damaged that type's interface by violating the single responsibility principle.

But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

And that could lead to dancing?  What exactly is the problem here?  It makes that much of a difference to deny saying Object(a, b, c) and forcing people to say Apply(Object, a, b, c)?

Yes! If I wanted `Object(a, b, c)` to be a meaningful interface in my class, then I would have put it there.

Your logic could be just as easily used to say that we should allow anyone to inject any member function into any class type we want. If that's what you want, then that is what should be proposed. And if we don't want that, then we definitely don't want `operator()` overloading, which is the effective equivalent of it.

As is, a type that has a member function template makes even its private members accessible to everyone, so privacy is dead anyway:

No, privacy is not dead. It is still restricted to just that template. Sure, users can specialize that template using types that didn't exist when you wrote it. But privacy is still restricted to exactly and only those template specializations. It doesn't go everywhere.

And even if it did, your argument is essentially that every class should be open to injection of any member functions that anyone likes.

Richard Smith

unread,
Jun 23, 2017, 8:23:10 PM6/23/17
to std-pr...@isocpp.org
On 23 June 2017 at 10:03, <jmo...@aldebaran.com> wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Overloading operator-> would mean that there can be a set of possible operator-> functions for a single type, with different return types. What do you expect to happen there? Should the selected operator-> depend on whether the type contains the named member? (You might like to look at the 'operator.' wording to get some idea of how complicated this can become.)

Overloading operator() sounds like it would potentially lead to a much more expensive overload resolution process for potentially every function call in the program, unless it's handled very carefully. For instance, someone is going to write something like this:

  template<typename R, typename ...T, typename ...U,
           enable_if<each_U_is_either_T_or_optional_T<list<T...>, list<U...>>::value, int> = 0>
  optional<R> operator()(R(*f)(T...), U &&...u) {
    /* if any u's are nullopt, return nullopt, otherwise return f(unwrap(forward<U>(u))) */
  }

... in order to allow:

  optional<int> a, b;
  int f(int x, int y);
  optional<int> c = f(a, b); // implicitly use ::operator() here, only call 'f' if neither 'a' nor 'b' is nullopt

... and likewise for a bunch of other templates that are functors in the category theory sense.

And now we potentially have a large number of operator() overloads to consider for every single function call, and considering each of them requires some nontrivial template instantiation work.

Overloading operator[] seems fine, though. It's just a normal binary operator with slightly funny syntax, after all.
 
Thank you in advance for your reviews and comments.

This email and any attachment thereto are confidential and intended solely for the use of the individual or entity to whom they are addressed.
If you are not the intended recipient, please be advised that disclosing, copying, distributing or taking any action in reliance on the contents of this email is strictly prohibited. In such case, please immediately advise the sender, and delete all copies and attachment from your system.
This email shall not be construed and is not tantamount to an offer, an acceptance of offer, or an agreement by SoftBank Robotics Europe on any discussion or contractual document whatsoever. No employee or agent is authorized to represent or bind SoftBank Robotics Europe to third parties by email, or act on behalf of SoftBank Robotics Europe by email, without express written confirmation by SoftBank Robotics Europe’ duly authorized representatives.


Ce message électronique et éventuelles pièces jointes sont confidentiels, et exclusivement destinés à la personne ou l'entité à qui ils sont adressés.
Si vous n'êtes pas le destinataire visé, vous êtes prié de ne pas divulguer, copier, distribuer ou prendre toute décision sur la foi de ce message électronique. Merci d'en aviser immédiatement l'expéditeur et de supprimer toutes les copies et éventuelles pièces jointes de votre système.
Ce message électronique n'équivaut pas à une offre, à une acceptation d’offre, ou à un accord de SoftBank Robotics Europe sur toute discussion ou document contractuel quel qu’il soit, et ne peut être interprété comme tel. Aucun employé ou agent de SoftBank Robotics Europe n'est autorisé à représenter ou à engager la société par email, ou à agir au nom et pour le compte de la société par email, sans qu’une confirmation écrite soit donnée par le représentant légal de SoftBank Robotics Europe ou par toute autre personne ayant reçu délégation de pouvoir appropriée.

--
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/8bd2a86c-2b01-491b-8ee9-30d78aa4b35e%40isocpp.org.

jmo...@aldebaran.com

unread,
Jun 24, 2017, 12:26:52 PM6/24/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com


Le vendredi 23 juin 2017 21:59:24 UTC+2, Barry Revzin a écrit :

On Friday, June 23, 2017 at 12:03:38 PM UTC-5, jmo...@aldebaran.com wrote:
The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

The examples for non-member operator[] and operator() seem pretty uncompelling. The first one seems like a bad algorithm (why not just pass in a predicate instead of enforcing indexing? It'd probably be less typing overall), the second one doesn't even directly use your operator() anywhere - it's buried... somewhere. I think these could use much stronger examples of cases where the best solution would be to add the overload, but we can't, so we're left with some subpar solution. And provide before/after examples demonstrating the improvement. Otherwise, right now it's kind of... meh. 

About the first example, you could also use operator[] to modify / create an element à la std::map. In this case, a predicate would not be enough. `[]` could be used as a "standard" way to access an element. But in the current state of the language, while designing a concept it is best to avoid relying on [] and (), if you want your concepts to be as easy to model as possible: if operator() and operator[] were not defined by the creator of a type, modeling the concept implies to duplicate the type (and its interface) and to add the operators. This is too much work compared to be able to define these operators as non-members.
So when designing concepts, an alternative is to use free functions (at() and call() for example), which is less natural, less concise and not compatible with builtin types.

The second example relies on function composition. If you don't have function objects, your function composition tools are unusable... Having to duplicate types instead of slightly adapting them is too bad, especially when you can do so with other operators.

For operator->(), that one is fundamentally different. Either the class is designed to be a proxy pointer/iterator... or not. What would it mean to add operator->() to some other class? Do you have an example for wanting to do this? 

Currently, it is possible to implement operator* as a non-member but not operator->, which seems strange.
You often have types with methods get(), load(), value(), etc. An interesting syntax unification is to provide operator* for these types, that just forward to get() / load() / value(). Then, if you have operator* that returns an l-value, why not having operator-> ? It would allow to have the syntax (*my_object).method() equivalent to my_object->method(), as expected.


Here is another example of adapting an existing type with non-member operators. This one is currently possible, since it uses arithmetical operators:

The creator of a type is responsible for the interface she provides.
But the user can decide that in her context some operators not provided by the creator of the type are meaningful.
For example, std::array forms a mathematical vector space with most arithmetical types. Roughly speaking, this means that an std::array can represent a mathematical vector and we just have to choose an appropriate scalar type.
std::array has already the right behavior (value semantics, etc.), except for the absence of arithmetical operators (+, *, etc.).
The user can't modify std::array definition, but she can add these operators as non-members.
For example:

// In the user's context, adaptation of std::array to model the VectorSpace concept.

// AdditiveSemigroup T
template<typename T, std::size_t N>
auto operator+(std::array<T, N> a, std::array<T, N> const& b) {
    std::transform(begin(a), end(a), begin(b), begin(a), std::plus<T>{});
    return a;
}

// VectorSpace (T, S) (T is the vector type, S is the scalar type)
template<typename S, typename T, std::size_t N>
auto operator*(S const& s, std::array<T, N> a) {
    std::transform(begin(a), end(a), begin(a), [=](auto const& x) {
        return s * x;
    });
    return a;
}

The user can also add @= versions (+=, *=, etc.) for efficiency reasons.

Now the user can use std::array as a vector space in her generic algorithms.
The amount of work is minimal because of the possibility to define
non-member operators.

For example (std::array<float, N>, int) forms a vector space.
So if v0, v1 are std::array<float, N> and k is an int, this kind of
expression is valid:

auto v2 = k * (v0 + v1);

Types are tools to be used by the user in whatever way makes sense in her context.
The creator of a type cannot imagine all these contexts, and shouldn't artificially
limit the user's possibilities.
C++ has the right approach concerning most operators (+, -, etc.), so why operator(), operator[] and operator-> should be exceptions?

Whatever the quality of the examples, I see this proposal as a way to fix an anomaly in the language.
In my opinion, in the design of the language consistency should be the default, and each non-consistent behavior should be motivated, not the opposite.

jmo...@aldebaran.com

unread,
Jun 24, 2017, 12:47:04 PM6/24/17
to ISO C++ Standard - Future Proposals


Le samedi 24 juin 2017 02:23:10 UTC+2, Richard Smith a écrit :
On 23 June 2017 at 10:03, <jmo...@aldebaran.com> wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Overloading operator-> would mean that there can be a set of possible operator-> functions for a single type, with different return types. What do you expect to happen there? Should the selected operator-> depend on whether the type contains the named member? (You might like to look at the 'operator.' wording to get some idea of how complicated this can become.)

Currently, operator-> must be defined as a non-static member function taking no parameters (16.5.6) . The non-member form would take a single argument evaluating to a class object, so there would be only one version by type, if I understand correctly.

Overloading operator() sounds like it would potentially lead to a much more expensive overload resolution process for potentially every function call in the program, unless it's handled very carefully. For instance, someone is going to write something like this:

  template<typename R, typename ...T, typename ...U,
           enable_if<each_U_is_either_T_or_optional_T<list<T...>, list<U...>>::value, int> = 0>
  optional<R> operator()(R(*f)(T...), U &&...u) {
    /* if any u's are nullopt, return nullopt, otherwise return f(unwrap(forward<U>(u))) */
  }

... in order to allow:

  optional<int> a, b;
  int f(int x, int y);
  optional<int> c = f(a, b); // implicitly use ::operator() here, only call 'f' if neither 'a' nor 'b' is nullopt

... and likewise for a bunch of other templates that are functors in the category theory sense.

And now we potentially have a large number of operator() overloads to consider for every single function call, and considering each of them requires some nontrivial template instantiation work.

I'm not sure if this example work (f is not a member function for example).
Anyway, it would be the user responsibility to add or not such an overload. And it is already possible to seriously slow down compilation by providing a bunch of template "enabled-if" operators (+, -, <<, and the others).
 
Overloading operator[] seems fine, though. It's just a normal binary operator with slightly funny syntax, after all.
 
Thank you in advance for your reviews and comments.

This email and any attachment thereto are confidential and intended solely for the use of the individual or entity to whom they are addressed.
If you are not the intended recipient, please be advised that disclosing, copying, distributing or taking any action in reliance on the contents of this email is strictly prohibited. In such case, please immediately advise the sender, and delete all copies and attachment from your system.
This email shall not be construed and is not tantamount to an offer, an acceptance of offer, or an agreement by SoftBank Robotics Europe on any discussion or contractual document whatsoever. No employee or agent is authorized to represent or bind SoftBank Robotics Europe to third parties by email, or act on behalf of SoftBank Robotics Europe by email, without express written confirmation by SoftBank Robotics Europe’ duly authorized representatives.


Ce message électronique et éventuelles pièces jointes sont confidentiels, et exclusivement destinés à la personne ou l'entité à qui ils sont adressés.
Si vous n'êtes pas le destinataire visé, vous êtes prié de ne pas divulguer, copier, distribuer ou prendre toute décision sur la foi de ce message électronique. Merci d'en aviser immédiatement l'expéditeur et de supprimer toutes les copies et éventuelles pièces jointes de votre système.
Ce message électronique n'équivaut pas à une offre, à une acceptation d’offre, ou à un accord de SoftBank Robotics Europe sur toute discussion ou document contractuel quel qu’il soit, et ne peut être interprété comme tel. Aucun employé ou agent de SoftBank Robotics Europe n'est autorisé à représenter ou à engager la société par email, ou à agir au nom et pour le compte de la société par email, sans qu’une confirmation écrite soit donnée par le représentant légal de SoftBank Robotics Europe ou par toute autre personne ayant reçu délégation de pouvoir appropriée.

--
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-proposal...@isocpp.org.

Nicol Bolas

unread,
Jun 24, 2017, 1:47:45 PM6/24/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Saturday, June 24, 2017 at 12:26:52 PM UTC-4, jmo...@aldebaran.com wrote:
Le vendredi 23 juin 2017 21:59:24 UTC+2, Barry Revzin a écrit :

On Friday, June 23, 2017 at 12:03:38 PM UTC-5, jmo...@aldebaran.com wrote:
The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

The examples for non-member operator[] and operator() seem pretty uncompelling. The first one seems like a bad algorithm (why not just pass in a predicate instead of enforcing indexing? It'd probably be less typing overall), the second one doesn't even directly use your operator() anywhere - it's buried... somewhere. I think these could use much stronger examples of cases where the best solution would be to add the overload, but we can't, so we're left with some subpar solution. And provide before/after examples demonstrating the improvement. Otherwise, right now it's kind of... meh. 

About the first example, you could also use operator[] to modify / create an element à la std::map. In this case, a predicate would not be enough. `[]` could be used as a "standard" way to access an element.

To access an element of what? You couldn't use it to access an element from `list`, or from any of the `set`s.

The rules that STL devised for interfaces give `operator[]` a special meaning for sequence containers. It means that access to the element is (amortized) O(1). If a container cannot fulfill that promise, then it ought not provide `operator[]`.

That operator is not meant to be a catch-all means of "accessing an element". Trying to use it that way is a bad thing.

We already have ways of accessing the i-th element of a forward range: you use iterators and `std::advance`. This is a good thing, because it allows different types to exhibit the behavior that works best for them.

But in the current state of the language, while designing a concept it is best to avoid relying on [] and (), if you want your concepts to be as easy to model as possible: if operator() and operator[] were not defined by the creator of a type,

Why did the creator of the type not implement `operator[]`? Was it because the access would not be O(1), and therefore the type shouldn't be used with algorithms that expect O(1) access? Why do you assume that the lack of `operator[]` was an accident or oversight, rather than a deliberate piece of design to prevent people from thinking of the operation incorrectly?

modeling the concept implies to duplicate the type (and its interface) and to add the operators. This is too much work compared to be able to define these operators as non-members.
So when designing concepts, an alternative is to use free functions (at() and call() for example), which is less natural, less concise and not compatible with builtin types.

What concepts are you talking about? Range TS, the largest body of concepts before the standard committee, includes no such thing. The closest it gets is Invocable, which relies on the definition of `std::invoke`. And the only reason Invocable it doesn't use `()` specifically is because `()` doesn't work on member pointers (which is why `std::invoke` exists in the first place). An object with a `call()` function is not Invocable. And `std::invoke` is not meant to be a customization point.

Can you give an example of such a concept? One that fits into the standard C++ paradigm?

The second example relies on function composition. If you don't have function objects, your function composition tools are unusable... Having to duplicate types instead of slightly adapting them is too bad, especially when you can do so with other operators.

If a type is not a functor, then how would you "adapt it" to an interface that accepts functors? If that type wasn't a functor, then it presumably has quite a few operations that you might want to call. How do you decide which operation you want to call in the `operator()` overload?

And how do you prevent conflicts? If generic system inserts its `operator()` overload for one operation, what happens when another system inserts another `operator()` overload into the type which happens to match the call signature? Or are they expected to use tag types to differentiate their particular calls?

At which point, there is fundamentally no difference between overloading `operator()` and simply inserting arbitrary functions into a class's member function list.

Also, why is there a need to duplicate a type to do composition? Lambdas can handle that adequately enough. After all, you're picking a single function to call anyway, and that determination is made at the point where you need to use it. This works a lot better as a lambda than by creating a type and forwarding functions. After all, your functor doesn't need to expose everything the type does; only that one specific operation.


For operator->(), that one is fundamentally different. Either the class is designed to be a proxy pointer/iterator... or not. What would it mean to add operator->() to some other class? Do you have an example for wanting to do this? 

Currently, it is possible to implement operator* as a non-member but not operator->, which seems strange.

Well yes; we ought to have forbidden that too. But the deed is done; there's no reason to compound the mistake just for the sake of consistency.

You often have types with methods get(), load(), value(), etc. An interesting syntax unification is to provide operator* for these types, that just forward to get() / load() / value().

Yes, that is very interesting. It's also a very bad idea. Most types that implement operator* don't throw exceptions if the value isn't there. `optional::value` does. As such, it is important that readers of the code see `value`, since that represents a possible site of an exception. By contrast, `optional::operator*` does not throw exceptions. So `::value` and `::operator*` are not the same thing at all.

And `std::future` is a great example of why. It does not have an `operator*`. Why? Because the act of calling `get` may throw an exception; the ability to pass exceptions through `promise`/`future`s is an important feature. And since `operator*` isn't supposed to throw exceptions, there is no such overload.

Now, users could write an `operator*` for `std::future`. How exactly would you implement this? Would you call `future::get` and let its exception bubble up, in violation of the expectation of `operator*`? Would you call `future::get` and swallow any exceptions? If it's the latter, how do you construct the return value if an exception manifests? And so forth.

The idea that you can force all of these things into the same `operator*` or `operator->` box is decidedly reductive.

Then, if you have operator* that returns an l-value, why not having operator-> ? It would allow to have the syntax (*my_object).method() equivalent to my_object->method(), as expected.


Here is another example of adapting an existing type with non-member operators. This one is currently possible, since it uses arithmetical operators:

The creator of a type is responsible for the interface she provides.
But the user can decide that in her context some operators not provided by the creator of the type are meaningful.
For example, std::array forms a mathematical vector space with most arithmetical types. Roughly speaking, this means that an std::array can represent a mathematical vector and we just have to choose an appropriate scalar type.
std::array has already the right behavior (value semantics, etc.), except for the absence of arithmetical operators (+, *, etc.).
The user can't modify std::array definition, but she can add these operators as non-members.
For example:
 
<snip>

The user can also add @= versions (+=, *=, etc.) for efficiency reasons.

Now the user can use std::array as a vector space in her generic algorithms.
The amount of work is minimal because of the possibility to define
non-member operators.

Yes, and this is a terrible idea. Why? Because it creates a huge mess of the meaning of the type system.

An array is not conceptually an addable thing. Therefore, `std::array`, which models the concept of an array, is not a thing that should be addable. A mathematical vector is conceptually an addable thing. Therefore, a type implementing that concept should be addable.

Now, that vector type may in fact be implemented in terms of `array`; that's fine. But there's a difference between saying that an array is an implementation detail of a vector and that all arrays are vectors. Not every array is a vector, and therefore not every array should be able to be treated as if it were a vector.

What you're doing is a violation of the Interface Segregation Principle; it's the generic programming equivalent of a fat interface base class. You make every array conceptually a vector, even if the user doesn't want or need it to be a vector.

We should not promote coding styles that violate things like this. We allow the extension of binary math operators outside of a class, not because we want people to inject their interfaces into other people's classes, but to allow for two different types to be addable. To allow adding scalars and vectors, for example.

This is not a tool that should be used to make a class which is not conceptually addable into one that is.

Richard Smith

unread,
Jun 24, 2017, 7:18:39 PM6/24/17
to std-pr...@isocpp.org
On 24 June 2017 at 09:47, <jmo...@aldebaran.com> wrote:
Le samedi 24 juin 2017 02:23:10 UTC+2, Richard Smith a écrit :
On 23 June 2017 at 10:03, <jmo...@aldebaran.com> wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Overloading operator-> would mean that there can be a set of possible operator-> functions for a single type, with different return types. What do you expect to happen there? Should the selected operator-> depend on whether the type contains the named member? (You might like to look at the 'operator.' wording to get some idea of how complicated this can become.)

Currently, operator-> must be defined as a non-static member function taking no parameters (16.5.6) . The non-member form would take a single argument evaluating to a class object, so there would be only one version by type, if I understand correctly.

So in cases where multiple viable operator-> functions exist, the result would be ambiguity, independent of the identifier after the -> ? That would avoid this problem, but is notably a different choice than that made during the design of 'operator.'. Your paper at least would need to describe and explain this decision.

Overloading operator() sounds like it would potentially lead to a much more expensive overload resolution process for potentially every function call in the program, unless it's handled very carefully. For instance, someone is going to write something like this:

  template<typename R, typename ...T, typename ...U,
           enable_if<each_U_is_either_T_or_optional_T<list<T...>, list<U...>>::value, int> = 0>
  optional<R> operator()(R(*f)(T...), U &&...u) {
    /* if any u's are nullopt, return nullopt, otherwise return f(unwrap(forward<U>(u))) */
  }

... in order to allow:

  optional<int> a, b;
  int f(int x, int y);
  optional<int> c = f(a, b); // implicitly use ::operator() here, only call 'f' if neither 'a' nor 'b' is nullopt

... and likewise for a bunch of other templates that are functors in the category theory sense.

And now we potentially have a large number of operator() overloads to consider for every single function call, and considering each of them requires some nontrivial template instantiation work.

I'm not sure if this example work (f is not a member function for example).

I think I'm missing something -- why would it make a difference if 'f' is a member function?
 
Anyway, it would be the user responsibility to add or not such an overload. And it is already possible to seriously slow down compilation by providing a bunch of template "enabled-if" operators (+, -, <<, and the others).

The large number of operator<< overloads are already a very significant component of compile time for many programs. This has the potential to apply the same problem to all function calls. I have not seen any evidence that programmers take this into account when adding their neat new operator overload. You're right that it's not the language's job to nanny the programmer, but the principle that you don't pay for what you don't use should apply to compilation performance as well as runtime.

I think there's also a question of whether you'd apply operator() recursively: if we can rewrite f(a, b, c) as operator()(f, a, b, c), do we then need to check whether we need to rewrite *that* as operator()(operator(), f, a, b, c) and so on? Likewise, can a + b be interpreted as operator()(operator+, a, b)?
 
Overloading operator[] seems fine, though. It's just a normal binary operator with slightly funny syntax, after all.
 
Thank you in advance for your reviews and comments.

This email and any attachment thereto are confidential and intended solely for the use of the individual or entity to whom they are addressed.
If you are not the intended recipient, please be advised that disclosing, copying, distributing or taking any action in reliance on the contents of this email is strictly prohibited. In such case, please immediately advise the sender, and delete all copies and attachment from your system.
This email shall not be construed and is not tantamount to an offer, an acceptance of offer, or an agreement by SoftBank Robotics Europe on any discussion or contractual document whatsoever. No employee or agent is authorized to represent or bind SoftBank Robotics Europe to third parties by email, or act on behalf of SoftBank Robotics Europe by email, without express written confirmation by SoftBank Robotics Europe’ duly authorized representatives.


Ce message électronique et éventuelles pièces jointes sont confidentiels, et exclusivement destinés à la personne ou l'entité à qui ils sont adressés.
Si vous n'êtes pas le destinataire visé, vous êtes prié de ne pas divulguer, copier, distribuer ou prendre toute décision sur la foi de ce message électronique. Merci d'en aviser immédiatement l'expéditeur et de supprimer toutes les copies et éventuelles pièces jointes de votre système.
Ce message électronique n'équivaut pas à une offre, à une acceptation d’offre, ou à un accord de SoftBank Robotics Europe sur toute discussion ou document contractuel quel qu’il soit, et ne peut être interprété comme tel. Aucun employé ou agent de SoftBank Robotics Europe n'est autorisé à représenter ou à engager la société par email, ou à agir au nom et pour le compte de la société par email, sans qu’une confirmation écrite soit donnée par le représentant légal de SoftBank Robotics Europe ou par toute autre personne ayant reçu délégation de pouvoir appropriée.

--
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-proposal...@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/8bd2a86c-2b01-491b-8ee9-30d78aa4b35e%40isocpp.org.


This email and any attachment thereto are confidential and intended solely for the use of the individual or entity to whom they are addressed.
If you are not the intended recipient, please be advised that disclosing, copying, distributing or taking any action in reliance on the contents of this email is strictly prohibited. In such case, please immediately advise the sender, and delete all copies and attachment from your system.
This email shall not be construed and is not tantamount to an offer, an acceptance of offer, or an agreement by SoftBank Robotics Europe on any discussion or contractual document whatsoever. No employee or agent is authorized to represent or bind SoftBank Robotics Europe to third parties by email, or act on behalf of SoftBank Robotics Europe by email, without express written confirmation by SoftBank Robotics Europe’ duly authorized representatives.


Ce message électronique et éventuelles pièces jointes sont confidentiels, et exclusivement destinés à la personne ou l'entité à qui ils sont adressés.
Si vous n'êtes pas le destinataire visé, vous êtes prié de ne pas divulguer, copier, distribuer ou prendre toute décision sur la foi de ce message électronique. Merci d'en aviser immédiatement l'expéditeur et de supprimer toutes les copies et éventuelles pièces jointes de votre système.
Ce message électronique n'équivaut pas à une offre, à une acceptation d’offre, ou à un accord de SoftBank Robotics Europe sur toute discussion ou document contractuel quel qu’il soit, et ne peut être interprété comme tel. Aucun employé ou agent de SoftBank Robotics Europe n'est autorisé à représenter ou à engager la société par email, ou à agir au nom et pour le compte de la société par email, sans qu’une confirmation écrite soit donnée par le représentant légal de SoftBank Robotics Europe ou par toute autre personne ayant reçu délégation de pouvoir appropriée.

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

jmo...@aldebaran.com

unread,
Jun 25, 2017, 2:52:34 PM6/25/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com

Please read the proposal for some answers to your questions.

The behavior of operator[] you mention is the one of the STL. But not all code in the world must follow STL design decisions.
For example in Element of Programming section 12.1, Stepanov describes a Linearizable concept in which `[]`'s complexity is not always O(1) but can be O(n) depending on the underlying iterator. It is his design decision to do so.

Frameworks and libraries in a given domain are not forced to follow STL, and can assign whatever semantics and algorithmic complexity to operators that makes sense in this domain.
One strength of C++ is that it doesn't impose a particular paradigm on the user. Instead of artificially forbidding things on user, the
 language should make as easy as possible the adaptation and composition of components (types, functions, concepts, etc.).

If in my context I want to use (std::array<float>, int) as a vector space (because it is mathematically well-defined and minimize my work), it is my responsibility to do so. If it makes sense in my context to adapt a type as a function object (because it enables monadic composition for example and minimize my work), it is my decision to do so and I shouldn't have to fight artificial limitations of the language.

jmo...@aldebaran.com

unread,
Jun 25, 2017, 2:56:39 PM6/25/17
to ISO C++ Standard - Future Proposals


Le dimanche 25 juin 2017 01:18:39 UTC+2, Richard Smith a écrit :
On 24 June 2017 at 09:47, <jmo...@aldebaran.com> wrote:
Le samedi 24 juin 2017 02:23:10 UTC+2, Richard Smith a écrit :
On 23 June 2017 at 10:03, <jmo...@aldebaran.com> wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

The main motivation for this proposal is consistency. It makes also easier to externally adapt existing types for use with generic algorithms, as show examples.

Overloading operator-> would mean that there can be a set of possible operator-> functions for a single type, with different return types. What do you expect to happen there? Should the selected operator-> depend on whether the type contains the named member? (You might like to look at the 'operator.' wording to get some idea of how complicated this can become.)

Currently, operator-> must be defined as a non-static member function taking no parameters (16.5.6) . The non-member form would take a single argument evaluating to a class object, so there would be only one version by type, if I understand correctly.

So in cases where multiple viable operator-> functions exist, the result would be ambiguity, independent of the identifier after the -> ? That would avoid this problem, but is notably a different choice than that made during the design of 'operator.'. Your paper at least would need to describe and explain this decision.

Yes there could be ambiguity, as with other operators.
The goal of the proposal is not to change the behavior of the operators themselves, it is only to relax the "non-static member" constraints (including on operator->).

Overloading operator() sounds like it would potentially lead to a much more expensive overload resolution process for potentially every function call in the program, unless it's handled very carefully. For instance, someone is going to write something like this:

  template<typename R, typename ...T, typename ...U,
           enable_if<each_U_is_either_T_or_optional_T<list<T...>, list<U...>>::value, int> = 0>
  optional<R> operator()(R(*f)(T...), U &&...u) {
    /* if any u's are nullopt, return nullopt, otherwise return f(unwrap(forward<U>(u))) */
  }

... in order to allow:

  optional<int> a, b;
  int f(int x, int y);
  optional<int> c = f(a, b); // implicitly use ::operator() here, only call 'f' if neither 'a' nor 'b' is nullopt

... and likewise for a bunch of other templates that are functors in the category theory sense.

And now we potentially have a large number of operator() overloads to consider for every single function call, and considering each of them requires some nontrivial template instantiation work.

I'm not sure if this example work (f is not a member function for example).

I think I'm missing something -- why would it make a difference if 'f' is a member function?

You're right, I misread.
 
 
Anyway, it would be the user responsibility to add or not such an overload. And it is already possible to seriously slow down compilation by providing a bunch of template "enabled-if" operators (+, -, <<, and the others).

The large number of operator<< overloads are already a very significant component of compile time for many programs. This has the potential to apply the same problem to all function calls. I have not seen any evidence that programmers take this into account when adding their neat new operator overload. You're right that it's not the language's job to nanny the programmer, but the principle that you don't pay for what you don't use should apply to compilation performance as well as runtime.

You pay only if you add such template operators. The situation is currently the same with a lot of operators (+, <<, etc.), so I don't see a particular problem here. Also most of the time, only a non-template version is needed (see examples of the proposal).

I think there's also a question of whether you'd apply operator() recursively: if we can rewrite f(a, b, c) as operator()(f, a, b, c), do we then need to check whether we need to rewrite *that* as operator()(operator(), f, a, b, c) and so on? Likewise, can a + b be interpreted as operator()(operator+, a, b)?

I'm not sure to understand: to me we are in the usual overload resolution mechanism (16.3.3).

Regards.

 

Klaim - Joël Lamotte

unread,
Jun 25, 2017, 4:49:34 PM6/25/17
to std-pr...@isocpp.org

On 23 June 2017 at 19:50, Nicol Bolas <jmck...@gmail.com> wrote:
OK, first question: what's the syntax for preventing people from overriding these things on my types?

Since C++11 we can "delete" free functions, operators included:

struct Foo
{
};

Foo operator+(const Foo&, const Foo&) = delete;

int main()
{
    Foo a, b;
    auto c = a + b; // error: use of deleted function
}


What more do you need to prevent people from overriding these things?

Joël Lamotte



FrankHB1989

unread,
Jul 2, 2017, 9:40:50 PM7/2/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com


在 2017年6月24日星期六 UTC+8上午5:17:59,Nicol Bolas写道:
On Friday, June 23, 2017 at 3:10:38 PM UTC-4, Hyman Rosen wrote:
On Fri, Jun 23, 2017 at 1:50 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

OK, first question: what's the syntax for preventing people from overriding these things on my types?

Wanting to prevent people from doing things is a bad idea, because the people who want to do things are living in the future of the people who want to prevent them from doing things, and the people in the future have better information about what they need than people in the past.

In any case, having those functions defined as non-members is no different from having any other functions or operators that take such objects as parameters.  I don't see why operator[] should be treated differently than operator+.

First, I contest the idea that `operator()` is no different from the others. You might argue that with `->` and `[]`, but not function call. The reason being that `operator()` has no parameter restrictions associated with it; you can overload it with any number of parameters of any type. `operator->` takes no parameters, and `operator[]` takes exactly one parameter. So the damage you can cause by external overloads is minimal.

But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

Second, my principle objection is that these operations are fundamentally part of the type. If a type is not a pointer-like type, then it has absolutely no business overloading `operator->`. If a type is not an array-like type, then it has no business overloading `operator[]`. And if a type is not a function-like type, then it has no business overloading `operator()`. And these are fundamental properties of the type; they're not something you can just graft onto any old type you like.
I don't think so. Overloading as construction of  EDSL (e.g. in boost.spirit) relies on violating your principles quite heavily.

And you even do not have ways to ensure least astonishment to well-educated newbies. Arguably, what is, say, an array-like type? How to make sure a type is like an array enough? No doubtfully `std::array` is like an array enough, but what like `std::map`? Why no `const` on its `operator[]`?

If you merely need to encode additional information like intention of the API to avoid misuse, there are other ways, like attributes.

Nicol Bolas

unread,
Jul 2, 2017, 11:11:49 PM7/2/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Sunday, July 2, 2017 at 9:40:50 PM UTC-4, FrankHB1989 wrote:
在 2017年6月24日星期六 UTC+8上午5:17:59,Nicol Bolas写道:
On Friday, June 23, 2017 at 3:10:38 PM UTC-4, Hyman Rosen wrote:
On Fri, Jun 23, 2017 at 1:50 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

OK, first question: what's the syntax for preventing people from overriding these things on my types?

Wanting to prevent people from doing things is a bad idea, because the people who want to do things are living in the future of the people who want to prevent them from doing things, and the people in the future have better information about what they need than people in the past.

In any case, having those functions defined as non-members is no different from having any other functions or operators that take such objects as parameters.  I don't see why operator[] should be treated differently than operator+.

First, I contest the idea that `operator()` is no different from the others. You might argue that with `->` and `[]`, but not function call. The reason being that `operator()` has no parameter restrictions associated with it; you can overload it with any number of parameters of any type. `operator->` takes no parameters, and `operator[]` takes exactly one parameter. So the damage you can cause by external overloads is minimal.

But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

Second, my principle objection is that these operations are fundamentally part of the type. If a type is not a pointer-like type, then it has absolutely no business overloading `operator->`. If a type is not an array-like type, then it has no business overloading `operator[]`. And if a type is not a function-like type, then it has no business overloading `operator()`. And these are fundamental properties of the type; they're not something you can just graft onto any old type you like.
I don't think so. Overloading as construction of  EDSL (e.g. in boost.spirit) relies on violating your principles quite heavily.

My argument is not that you shouldn't be able to overload any operator from outside the type. My argument is that you shouldn't be able to overload those operators from outside of the type. If the maker of a type didn't give it an `operator[]` overload, you don't have the right to impose one upon it.

We've been able to make EDSLs despite being unable to overload those operators from outside the type.
 
And you even do not have ways to ensure least astonishment to well-educated newbies. Arguably, what is, say, an array-like type? How to make sure a type is like an array enough? No doubtfully `std::array` is like an array enough, but what like `std::map`? Why no `const` on its `operator[]`?

It's interesting that you bring up `map`'s `operator[]`, because that was on my mind back when I posted that. If we had it to do over again, I rather suspect that `map` would not have an `operator[]` overload. We've had to patch `map`'s design just to address deficiencies in `operator[]`, with `try_emplace` and `insert_or_assign` effectively doing much the job of `operator[]` much better than that function ever could.

If we still had to keep it around, it'd probably be a `const` function.

FrankHB1989

unread,
Jul 2, 2017, 11:44:50 PM7/2/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com


在 2017年6月24日星期六 UTC+8上午7:40:35,Nicol Bolas写道:
On Friday, June 23, 2017 at 6:51:43 PM UTC-4, Hyman Rosen wrote:
On Fri, Jun 23, 2017 at 5:17 PM, Nicol Bolas <jmck...@gmail.com> wrote:
So the damage you can cause

What makes it "damage"?

You have taken a type which was not a functor and made it a functor. You have taken a type that was not array-like or pointer-like and made it array/pointer-like.

If those are not the purpose of those types, then you have damaged that type's interface by violating the single responsibility principle.

You are assuming the functionality implemented by overloading as "responsibility". But the language rules does not work like that. There are no disciplines enforced by the language to make syntactic forms coming with specific semantics restrictions tightly in general (more than special rules like C#'s composition of overloaded operators). They are all up to the users. What you suggested is assuming implicit requirements on allowed types, that does not improve readability or productivity by itself. If you do need it, propose it to make the spec of the language agree.

Nevertheless, to provide overloading functions by the author of the class type or not, is not relevant much here, and I am not strongly against it.

FrankHB1989

unread,
Jul 3, 2017, 12:44:36 AM7/3/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com


在 2017年7月3日星期一 UTC+8上午11:11:49,Nicol Bolas写道:
On Sunday, July 2, 2017 at 9:40:50 PM UTC-4, FrankHB1989 wrote:
在 2017年6月24日星期六 UTC+8上午5:17:59,Nicol Bolas写道:
On Friday, June 23, 2017 at 3:10:38 PM UTC-4, Hyman Rosen wrote:
On Fri, Jun 23, 2017 at 1:50 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Hello,

This is a proposal to allow operator(), operator[] and operator-> to be defined as non-member functions.
I do not know if this has already been proposed and potential arguments against it, so please let me know.

OK, first question: what's the syntax for preventing people from overriding these things on my types?

Wanting to prevent people from doing things is a bad idea, because the people who want to do things are living in the future of the people who want to prevent them from doing things, and the people in the future have better information about what they need than people in the past.

In any case, having those functions defined as non-members is no different from having any other functions or operators that take such objects as parameters.  I don't see why operator[] should be treated differently than operator+.

First, I contest the idea that `operator()` is no different from the others. You might argue that with `->` and `[]`, but not function call. The reason being that `operator()` has no parameter restrictions associated with it; you can overload it with any number of parameters of any type. `operator->` takes no parameters, and `operator[]` takes exactly one parameter. So the damage you can cause by external overloads is minimal.

But `operator()` can take anything; you can inject entire interfaces into a type through clever use of tag types. Overloading `operator()` from outside a type is functionally equivalent to allowing you to add (non-friend) member functions to a type.

Second, my principle objection is that these operations are fundamentally part of the type. If a type is not a pointer-like type, then it has absolutely no business overloading `operator->`. If a type is not an array-like type, then it has no business overloading `operator[]`. And if a type is not a function-like type, then it has no business overloading `operator()`. And these are fundamental properties of the type; they're not something you can just graft onto any old type you like.
I don't think so. Overloading as construction of  EDSL (e.g. in boost.spirit) relies on violating your principles quite heavily.

My argument is not that you shouldn't be able to overload any operator from outside the type. My argument is that you shouldn't be able to overload those operators from outside of the type. If the maker of a type didn't give it an `operator[]` overload, you don't have the right to impose one upon it.

Well, that's somewhat OK. But the point has diverged.

We've been able to make EDSLs despite being unable to overload those operators from outside the type.
 
And you even do not have ways to ensure least astonishment to well-educated newbies. Arguably, what is, say, an array-like type? How to make sure a type is like an array enough? No doubtfully `std::array` is like an array enough, but what like `std::map`? Why no `const` on its `operator[]`?

It's interesting that you bring up `map`'s `operator[]`, because that was on my mind back when I posted that. If we had it to do over again, I rather suspect that `map` would not have an `operator[]` overload. We've had to patch `map`'s design just to address deficiencies in `operator[]`, with `try_emplace` and `insert_or_assign` effectively doing much the job of `operator[]` much better than that function ever could.

Yes, interesting. But on this topic I don't follow you. This is because you seem to suggest array as C-like (or std::vector-like, providing some assumptions of memory layout among elements) ones, but it need not to be true.

In fact, the built-in [] in C and C++ relies on semantics of pointers, which is not specific to abstraction exposed by the notion "array" (in general) at all, just implementation details of the specific builtin types in C and C++. Arguably, I think C's builtin array named badly. Those types should better be called "vectors", following the prior art of B language, also reducing the confusion with notions from math (despite of Iliffe vector in Java, etc). (And `std::vector` should have better been `std::array`, `std::dynarray` or something else.) On the other hand, `map` or `unordered_map` are instances of associative arrays, or something indexable, which catches the notion behind "array" or [] well among all kinds of C-like languages (including those without pointers). Overloading of `operator[]` would perfectly make `[]` bind with those types and provide the the principal method of element access in a neat way.

If we still had to keep it around, it'd probably be a `const` function.
 
To fix the const qualifier inconsistency is not as important as this abstraction; as long as the const-correctness being well kept, why force `const` on `[]`? Index with `const` is a more detailed capability only supported by some subsets of associative arrays and something supported by builtin array types are just not so generic, that's all.

Moreover, similar confusions also exist in the term "function". How do you define the term "function"? If you take the builtin ones, lack of first-class function in C has already make the thing in a mess. Since a first-class function (implemented as closures, in general) significantly needs more complicated data structures rather than a wrapper like `std::array`, the terminology divergence has more damage on well-defined notions from history of computer science. Things are even worse because there are more complex problems on functions besides first-classness. For example, reference transparency or more aggressively, "purity", which is sensible in the origin of the term "function" (in math). Just because this property has widely broken in languages with side effects, you may not care it at all when talking with people only with a background of ALGOL-like languages, but it is still a serious problem to draft the specification once you want the language supporting multiple paradigms. Another example, the evaluation strategy, which should be a new orthogonal aspect of function of C++. Functions in some other languages support call-by-need and even more esoteric ones, and are they allowed to be introduced in a future version of C++, despite there are something more similar already called as "macros"?

I don't think all of them are interesting and meaningful to C++, but I still don't think to limit extentions of terms as you think is a right way to work with the spec of a general-purposed language.


Matthew Woehlke

unread,
Aug 8, 2017, 1:37:51 PM8/8/17
to std-pr...@isocpp.org, jmo...@aldebaran.com
On 2017-06-25 14:56, jmo...@aldebaran.com wrote:
> Le dimanche 25 juin 2017 01:18:39 UTC+2, Richard Smith a écrit :
>> I think there's also a question of whether you'd apply operator()
>> recursively: if we can rewrite f(a, b, c) as operator()(f, a, b, c), do we
>> then need to check whether we need to rewrite *that* as
>> operator()(operator(), f, a, b, c) and so on? Likewise, can a + b be
>> interpreted as operator()(operator+, a, b)?
>
> I'm not sure to understand: to me we are in the usual overload resolution
> mechanism (16.3.3).

template <typename... Args>
auto operator()(Args...) { /* code here */ }

f(); // infinite recursion?

--
Matthew

olafv...@gmail.com

unread,
Aug 9, 2017, 8:13:23 AM8/9/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
I've wanted to use operator[] on some library classes before and couldn't.. until I took over maintenance of the library.

I think allowing those to be defined a free functions would be useful.

Nasos

unread,
Sep 11, 2017, 5:10:38 PM9/11/17
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
I would find this functionallity very handy. Especially when I want to provide my library users with the ability to extend relevant operators and I am not sure why they are currently restricted to member functions only.

I don't share any of the previous rejecting comments, especially  because (as others have mentioned)   other operators are allowed to be overloaded and even deleted. In addition the compiler can easily detect an ambiguous overload if one is attempted by mistake, so I don't see a problem there.

-Nasos




On Friday, June 23, 2017 at 1:03:38 PM UTC-4, jmo...@aldebaran.com wrote:
Reply all
Reply to author
Forward
0 new messages