P0700: operator.() vs. Delegation

359 views
Skip to first unread message

Nicol Bolas

unread,
Jun 27, 2017, 12:32:35 PM6/27/17
to ISO C++ Standard - Future Proposals
Stroustrup's P0700 (PDF) paper is a critique of P0352: Smart References through Delegation (PDF). As the author of the direct proposal for operator-dot P0416 (PDF), naturally Stroustrup doesn't have a particularly high opinion of P0352.

It's an interesting paper. Essentially, the foundational idea of P0352 is that you can achieve 99% of the goals of operator-dot without all of the baggage that operator-dot requires. You express it by piggy-backing off of inheritance; the proxied type is a base class of the proxy, except that it is not a base class subobject. The conversion from the proxy to the "base class" is user-provided rather than through the usual base class means.

This means that we don't have to rewrite the rules of what `x.y` means. Such an expression uses the exact same rules of lookup we currently have now. `x.y` would first look through derived class members, then base classes, and so forth.

Stroustrup's argument against P0352 is essentially that P0352 is all about the technical challenge (or specifically, about avoiding the technical challenge) of providing the functionality, rather than just rewriting whatever rules in C++ need to be rewritten. That is, if you have an overload of operator-dot, then you want `x.y` to always look `y` up in the proxied type.

Stroustrup has a point, with regard to P0352's lack of discussion of the usability of the proxy types it creates. However, I find that Stroustrup's argument misses one key point with regard to usability: the number of rules users have to know.

See, it's not a question of whether `++x` calls the proxy or the proxied type. To me, it's a question of why it calls that. Or more to the point, how easy it is to realize that `x.y` will call a member of the proxied type rather than the proxy.

With P0416, operator-dot, I now have to remember a bunch of new lookup rules. I have to check to see if a type overloads operator-dot. If it does, then I have to know exactly what the new rules are, that the proxied type takes priority. But these rules are complicated. There are times when the proxied type doesn't take priority (if I recall correctly, in constructors and destructors).  And more importantly, if I really want to access the proxy rather than the proxied type, I have to learn a complex new idiom: `std::addressof(x)->y`.

The advantage of P0352 is that I don't have new rules to learn. `x.y` follows C++ lookup rules that have existed since the day C++ was standardized. The derived class overrides the base class names. That's the rule, and we all know it by heart. So the proxy takes priority. And if you want to ensure access to proxied members, we already have an idiom for that: `x.ProxiedType::y`. That's all well understood C++, and requires no extension to the rules to achieve.

This is the "simplicity" of P0352 that Stroustrup did not seem to grasp. Now, it may not matter much to the average user in the field, since in many cases, both features will do the same thing. But in cases where there are genuine questions about which members are accessed, it is better to have one set of rules than to have two. And also, when it comes to implementing proxies, I think it will be very crucial to users to have an intuitive understanding of what their code is supposed to mean.

Though admittedly, I personally despise the P0416 design that gives the proxied type priority in every case (and I'm not a fan of overloading operator-dot to begin with). So that may just be my personal desire to see us gain this functionality in a way that doesn't require me to use `std::addressof` to access the proxy itself (particularly in its implementation).

Mathias Gaunard

unread,
Jun 27, 2017, 3:44:42 PM6/27/17
to std-pr...@isocpp.org
I had a proposal for operator dot as well, P0060,  but I just gave up on it since P0352 is just so much nicer: it just works.
Adopting it should be a no-brainer.

The other proposals just add a ton of complications and have a number of corner cases where they don't work as expected and don't satisfy the principle of least surprise.

A number of people on the committee seem to believe that adding complexity to the language is not a problem so long as that complexity is not exposed to the "everyday programmer". I think that's a terrible approach since any serious C++ developer, expert or not, needs to precisely understand what the code they're looking at is doing.
Consistent and simple rules go a long way, and inheritance has everything that we need already.

--
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/57d46356-3774-4c42-8ed5-2ffd369cca45%40isocpp.org.

Matt Calabrese

unread,
Jun 27, 2017, 4:41:13 PM6/27/17
to ISO C++ Standard - Future Proposals
On Tue, Jun 27, 2017 at 3:44 PM, Mathias Gaunard <mathias...@gmail.com> wrote:
A number of people on the committee seem to believe that adding complexity to the language is not a problem so long as that complexity is not exposed to the "everyday programmer". I think that's a terrible approach since any serious C++ developer, expert or not, needs to precisely understand what the code they're looking at is doing.

Yes, I also hate that kind of argument with a passion. It's just a license to make inconsistent and confusing rules under the guise of being "intuitive". Wanting simple or at the very least consistent rules is how to make the language easier to reason about.

Hyman Rosen

unread,
Jun 27, 2017, 5:39:27 PM6/27/17
to std-pr...@isocpp.org
On Tue, Jun 27, 2017 at 4:41 PM, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:
On Tue, Jun 27, 2017 at 3:44 PM, Mathias Gaunard <mathias...@gmail.com> wrote:
Yes, I also hate that kind of argument with a passion. It's just a license to make inconsistent and confusing rules under the guise of being "intuitive". Wanting simple or at the very least consistent rules is how to make the language easier to reason about.

It never worked for me when I was trying to convince people that strict left-to-right order of evaluation was simple and consistent.
Instead we got the rule that makes a = b, a + b, and a << b all have different order requirements.  P0352 seems really nice and clean to me.

Nicol Bolas

unread,
Jun 27, 2017, 6:40:06 PM6/27/17
to ISO C++ Standard - Future Proposals
On Tuesday, June 27, 2017 at 3:44:42 PM UTC-4, Mathias Gaunard wrote:
I had a proposal for operator dot as well, P0060,  but I just gave up on it since P0352 is just so much nicer: it just works.
Adopting it should be a no-brainer.

The other proposals just add a ton of complications and have a number of corner cases where they don't work as expected and don't satisfy the principle of least surprise.

I think Stroustrup would say that "expected" and "surprise" is in the eye of the beholder. That the beholder in this case should expect `Ref<X>` to behave exactly like `X`, unless either special syntax is used or `X` just doesn't have that particular behavior.

Personally, I think that's the wrong beholder to be looking at. Especially when you're implementing the `Ref` class itself; I think it's absolutely ridiculous to require using `std::addressof(*this)` just to access your own members.

A number of people on the committee seem to believe that adding complexity to the language is not a problem so long as that complexity is not exposed to the "everyday programmer". I think that's a terrible approach since any serious C++ developer, expert or not, needs to precisely understand what the code they're looking at is doing.

I don't think that every C++ programmer needs to precisely understand every aspect of their code (don't get me wrong; that would be a good thing; I just don't think it's absolutely essential). But I do agree that a feature which significantly magnifies the complexity of an already complex part of the language (ie: name lookup), over a few corner cases (ie: conflict between `X` and `Ref<X>`) is wrongheaded.

I think another pernicious problem with some committee members is an over-reliance on caring about what the user sees rather than what the implementer has to do. That is, the notion that the writers of code that implement a feature in a class should be assumed to be experts, while we should assume that the users of the class are not experts or even knowledgable about C++. This justifies make a feature arcane for the implementer of a type (see `std::addressof`).

Consistent and simple rules go a long way, and inheritance has everything that we need already.

To be fair, that rather depends on the goal. If the goal is for `Ref<X>` to give absolute primacy to `X`'s interface, then inheritance cannot do that. The question is whether this one corner case is important enough to create a complex series of rules to gain this behavior.

I also think that simple rules can create useful effects that we didn't intend. For example, `operator-dot` cannot allow an object to proxy multiple objects; multiple inheritance through `using` declarations and multiple conversion operators can. Indeed, it's theoretically possible to create a proxy `variant` that exposes the interface of all of its members. You can't do that with `operator-dot`, since that would require overloading on `operator.()`'s return value.

Arthur O'Dwyer

unread,
Jun 27, 2017, 7:15:38 PM6/27/17
to ISO C++ Standard - Future Proposals
Hear, hear.  I have not followed operator.() very closely because it generally does not matter to me, but I confess I found P0352R1 remarkably easy to grasp, and P0700 remarkably petty and whatever-the-word-is-for-not-making-a-valid-point.  Plus, on the technical merits, if typical usage of the feature doesn't involve the . character (but rather ++a and such other examples from the paper), I see no reason the feature should be defined with the syntax operator.. If the feature acts fundamentally like a user-defined type-conversion, I really really like the idea of spelling it like one.

One thing that I did take from Stroustrup's P0700, though, is that I'm still confused about what the desired behavior is if I have

template<class T> void f(T t);
void g(Ref<X> r);
Ref<X> rx;
f(rx);  // deduce T=Ref<X> or just X?
g(rx);  // pass a copy of rx, or pass Ref<X>(X(rx))?
auto r2 = rx;  // deduce Ref<X> or just X?

I would hope that the desired behavior is "the former, not the latter" in each case, but that seems too... reasonable?  I mean, isn't the whole point of operator.() to create confusion about what is and what isn't a reference?

One nice thing about P0352, that I might see a use for, is that it apparently re-enables the ability to "inherit from" final classes. A few nifty metaprogramming techniques went out the window with the introduction of the final qualifier, because you could no longer use the "inherit from user-defined T" trick to get T's members into the same scope as some other member of your own devising. But with P0352, we can get all those mechanisms back easily; final classes no longer have absolute power over library writers.

struct SpecialSnowflake final { };

struct Ha_Ha : public using SpecialSnowflake {
    SpecialSnowflake fake_base;
    operator SpecialSnowflake& () { return fake_base; }
};

–Arthur

C++: You accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical care is impossible since you can't tell which are bitwise copies and which are just pointing at others and saying "that's me, over there."

Mathias Gaunard

unread,
Jun 27, 2017, 7:21:34 PM6/27/17
to std-pr...@isocpp.org
On 27 June 2017 at 23:40, Nicol Bolas <jmck...@gmail.com> wrote:

I think Stroustrup would say that "expected" and "surprise" is in the eye of the beholder. That the beholder in this case should expect `Ref<X>` to behave exactly like `X`, unless either special syntax is used or `X` just doesn't have that particular behavior.

There is no such thing as a type behaving exactly like another type in C++, since lookup depends on type equality and not just structural equality.

Nicol Bolas

unread,
Jun 27, 2017, 7:28:09 PM6/27/17
to ISO C++ Standard - Future Proposals


On Tuesday, June 27, 2017 at 7:15:38 PM UTC-4, Arthur O'Dwyer wrote:
On Tuesday, June 27, 2017 at 1:41:13 PM UTC-7, Matt Calabrese wrote:
On Tue, Jun 27, 2017 at 3:44 PM, Mathias Gaunard <mathias...@gmail.com> wrote:
A number of people on the committee seem to believe that adding complexity to the language is not a problem so long as that complexity is not exposed to the "everyday programmer". I think that's a terrible approach since any serious C++ developer, expert or not, needs to precisely understand what the code they're looking at is doing.

Yes, I also hate that kind of argument with a passion. It's just a license to make inconsistent and confusing rules under the guise of being "intuitive". Wanting simple or at the very least consistent rules is how to make the language easier to reason about.

Hear, hear.  I have not followed operator.() very closely because it generally does not matter to me, but I confess I found P0352R1 remarkably easy to grasp, and P0700 remarkably petty and whatever-the-word-is-for-not-making-a-valid-point.  Plus, on the technical merits, if typical usage of the feature doesn't involve the . character (but rather ++a and such other examples from the paper), I see no reason the feature should be defined with the syntax operator.. If the feature acts fundamentally like a user-defined type-conversion, I really really like the idea of spelling it like one.

One thing that I did take from Stroustrup's P0700, though, is that I'm still confused about what the desired behavior is if I have

template<class T> void f(T t);
void g(Ref<X> r);
Ref<X> rx;
f(rx);  // deduce T=Ref<X> or just X?
g(rx);  // pass a copy of rx, or pass Ref<X>(X(rx))?
auto r2 = rx;  // deduce Ref<X> or just X?

I would hope that the desired behavior is "the former, not the latter" in each case, but that seems too... reasonable?  I mean, isn't the whole point of operator.() to create confusion about what is and what isn't a reference?

Well, the P0416 paper says "When  we  pass  a  smart  reference as  a  template  argument  or  initialize  an auto
object,  we  must  decide which type to deduce to: the handle or the value? We deduce to the handle."

Also, we already have a paper to allow the latter (at least, for the `auto r2 = rx;` case), so we don't need this feature to cause the former.

One nice thing about P0352, that I might see a use for, is that it apparently re-enables the ability to "inherit from" final classes. A few nifty metaprogramming techniques went out the window with the introduction of the final qualifier, because you could no longer use the "inherit from user-defined T" trick to get T's members into the same scope as some other member of your own devising. But with P0352, we can get all those mechanisms back easily; final classes no longer have absolute power over library writers.

I don't think P0352 should allow that. If a class is declared `final`, it seems decidedly poor form for us to allow it to be used as a base class, even if it is not a base class subobject. I mean, you can decide that class-scoped `final` is a bad idea, but if we're going to have it, it should actually work.

Richard Smith

unread,
Jun 27, 2017, 8:04:39 PM6/27/17
to std-pr...@isocpp.org
One thing I really like about P0352 is that it gives us control over virtual base classes. We don't have to accept the extra overhead that compilers introduce for a fully general virtual base class implementation, if (through knowledge of our own class hierarchy) we can do better.

–Arthur

C++: You accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical care is impossible since you can't tell which are bitwise copies and which are just pointing at others and saying "that's me, over there."

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

Bengt Gustafsson

unread,
Jun 27, 2017, 8:05:30 PM6/27/17
to ISO C++ Standard - Future Proposals
The point of P0352 is to not have to define any new rules. So all the examples Stroustrup was confused about work as if Ref<T> actually inherited T in the original sense.

This means "the former, not the latter" in each case, except if the expresstion-template conversion-to-auto operator is in the Ref<X> API, which may be appropriate for some kinds of references.

I'm unsure whether this should be viewed as inheritance-like enough to disallow it for final classes. Initially I was inclined to agree with Nicol but on the other hand it seems strange to not be able to create a Ref<T> just because T is final. Noone would expect this to be forbidden and in the alternate operator.() implementation it would definitely not be forbidden. So I changed my mind and now think that Arthur should be allowed to create a by-value member and refer to it.

One point I agree with Stroustrup about is a bit of unease with the need for mentioning the refered type twice. Unfortunately my suggested syntax has the same flaw, except if use auto as the return type of the refered function:

template<typename T> class Ref : public T(get_ref) {
     T* m_ptr;
     auto get_ref() { return *m_ptr; }
};

So the syntax idea was to explicitly mention a named method in the baseclass list instead of (re)using the using keyword yet again.

Semantically I see no difference, except possibly leaving the operator T&() signature open for other uses, but this would be very confusing on the other hand.

Initially I wanted to use a & on the base type to indicate "inheritance by reference" but then I had no way to specify the function (or so I thought). The use of a conversion operator seems like the right tool, I think maybe that:

template<typename T> class Ref : public T& {
     operator T&() { ... }
};

could be more intuitive than the using keyword, but whatever syntax I have a strong feeling that P0352 is the right way to go rather than operator.() with any semantics. Lets help clarifying the paper in a new version with more use cases to make it more convincing to all readers.

Bengt Gustafsson

unread,
Jun 27, 2017, 8:10:22 PM6/27/17
to ISO C++ Standard - Future Proposals
Just one more thing: One approach to cover up the inconsistency created if Ref<int> was allowed (which I think it must) would be to actually allow inheritance (yes, normal inheritance) from builtin types. I was expecting to easily find an example that shows why this is impossible, but I could not come up with any... so tell me why!

Arthur O'Dwyer

unread,
Jun 27, 2017, 10:40:00 PM6/27/17
to ISO C++ Standard - Future Proposals
On Tue, Jun 27, 2017 at 5:05 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
The point of P0352 is to not have to define any new rules. So all the examples Stroustrup was confused about work as if Ref<T> actually inherited T in the original sense.

This means "the former, not the latter" in each case, except if the expresstion-template conversion-to-auto operator is in the Ref<X> API, which may be appropriate for some kinds of references.

FWIW, I think I'm vaguely aware of "operator auto", but I don't understand what expression templates have to do with it.

[...]
One point I agree with Stroustrup about is a bit of unease with the need for mentioning the refered type twice. Unfortunately my suggested syntax has the same flaw, except if use auto as the return type of the refered function:

template<typename T> class Ref : public T(get_ref) {
     T* m_ptr;
     auto get_ref() { return *m_ptr; }
};

So the syntax idea was to explicitly mention a named method in the baseclass list instead of (re)using the using keyword yet again.

Even without "auto", this has the same problem w.r.t. the repetition of the word "get_ref". Also, I don't instantly intuitively understand what should happen if "get_ref" is an inherited member, or if I inherit from Ref<T> and override "get_ref" — I suspect it all falls out naturally but for some reason "operator T&" doesn't cause that mental block for me.
One benefit of the "get_ref" approach is that "get_ref" could be a static member function, whereas I suppose "operator T&" cannot be static.
Another benefit of the "get_ref" approach is that if we re-use "operator T&" for purposes of operator.(), then we're unnecessarily tying together the notions of "delegates-all-the-members-of-T-like-so" and "is-implicitly-convertible-to-T". I can easily imagine a "smart reference" type that doesn't want to be implicitly (nor explicitly) convertible to T&, just as I can easily imagine a "smart pointer" type that doesn't want to be implicitly (nor explicitly) convertible to T*.

 
Semantically I see no difference, except possibly leaving the operator T&() signature open for other uses, but this would be very confusing on the other hand.

The "operator T&" signature already has a meaning — implicit/explicit conversion to T& — which is why I'm suddenly a bit less keen on it. The idea of pulling names into scope via something-like-inheritance is still correct IMO, but I don't like tying the actual process of delegation to the result of a specific conversion operator.

 
Initially I wanted to use a & on the base type to indicate "inheritance by reference" but then I had no way to specify the function (or so I thought). The use of a conversion operator seems like the right tool, I think maybe that:

template<typename T> class Ref : public T& {
     operator T&() { ... }
};

Here you'd have to make a really convincing argument that "inherit from T&" shouldn't have some other meaning. I think you and I would probably agree that "inherit from int" has a reasonably intuitive meaning, and "inherit from pointer-to-int" has a reasonably intuitive meaning; but do our intuitions diverge at "inherit from reference-to-int"?  Don't answer that. ;)

Here's another addition to the syntax bikeshed:

template<typename T> class Ref {
private:
    T *p;
public:
    public T&() { return *p; }
    public const T&() const { return *p; }
};

The advantage of this syntax (without anything explicitly in the class header) is that you can have const and non-const versions that give different results; for example, giving back an iterator in one case and a const_iterator in another. I have no use-cases in mind.
The "public" here is meant to indicate that it works kind of like public inheritance; however, I have no idea what "private" would mean in the same context, given that the access control of the function is actually controlled by the "public:" on the preceding line. P0352R1 has the exact same problem: it's unclear what would happen if you wrote

template<typename T> class Ref : public using T {
private:
    T *p;
    operator T&() { return *p; }  // this is a private conversion operator!
};

(Does this Ref<T> behave like a smart reference only in friend contexts?)

–Arthur

Bengt Gustafsson

unread,
Jun 28, 2017, 5:45:02 AM6/28/17
to ISO C++ Standard - Future Proposals


Den onsdag 28 juni 2017 kl. 04:40:00 UTC+2 skrev Arthur O'Dwyer:
On Tue, Jun 27, 2017 at 5:05 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
The point of P0352 is to not have to define any new rules. So all the examples Stroustrup was confused about work as if Ref<T> actually inherited T in the original sense.

This means "the former, not the latter" in each case, except if the expresstion-template conversion-to-auto operator is in the Ref<X> API, which may be appropriate for some kinds of references.

FWIW, I think I'm vaguely aware of "operator auto", but I don't understand what expression templates have to do with it.
operator auto (at least the meaning I put into that notion) is a way to define a type that an object of the type having the operator will be converted to in case it is assigned to an auto variable.
The primary use case is for expression templates where you typically would like to evaluate the expression when assigning it to an auto variable:

auto m = A * B;

If A and B are matrices in a library with lazy evaluation you would expect m to be a matrix, not some obscure type in the expression template motor.


 

[...]
One point I agree with Stroustrup about is a bit of unease with the need for mentioning the refered type twice. Unfortunately my suggested syntax has the same flaw, except if use auto as the return type of the refered function:

template<typename T> class Ref : public T(get_ref) {
     T* m_ptr;
     auto get_ref() { return *m_ptr; }
};

So the syntax idea was to explicitly mention a named method in the baseclass list instead of (re)using the using keyword yet again.

Even without "auto", this has the same problem w.r.t. the repetition of the word "get_ref". Also, I don't instantly intuitively understand what should happen if "get_ref" is an inherited member, or if I inherit from Ref<T> and override "get_ref" — I suspect it all falls out naturally but for some reason "operator T&" doesn't cause that mental block for me.
I guess the usual lookup rules would apply, which would crate the possibility to actually refer to a method of the reffered object, which would not work out very well. So this is a drawback (unless conversion operators are inherited, which I don't remember if they are).
 
One benefit of the "get_ref" approach is that "get_ref" could be a static member function, whereas I suppose "operator T&" cannot be static.
I don't know if there is a rule to prevent static conversion operators.
 
Another benefit of the "get_ref" approach is that if we re-use "operator T&" for purposes of operator.(), then we're unnecessarily tying together the notions of "delegates-all-the-members-of-T-like-so" and "is-implicitly-convertible-to-T". I can easily imagine a "smart reference" type that doesn't want to be implicitly (nor explicitly) convertible to T&, just as I can easily imagine a "smart pointer" type that doesn't want to be implicitly (nor explicitly) convertible to T*.
Yes, but then maybe we could encode that by making the conversion operator private, with the view that when refering to the "base class" we do that as member of the class. Well, maybe not totally logical but it would be possible to define it that way.
 

 
Semantically I see no difference, except possibly leaving the operator T&() signature open for other uses, but this would be very confusing on the other hand.

The "operator T&" signature already has a meaning — implicit/explicit conversion to T& — which is why I'm suddenly a bit less keen on it. The idea of pulling names into scope via something-like-inheritance is still correct IMO, but I don't like tying the actual process of delegation to the result of a specific conversion operator.
Yes, I think we share a similar vague uneasiness about this then.

 

 
Initially I wanted to use a & on the base type to indicate "inheritance by reference" but then I had no way to specify the function (or so I thought). The use of a conversion operator seems like the right tool, I think maybe that:

template<typename T> class Ref : public T& {
     operator T&() { ... }
};

Here you'd have to make a really convincing argument that "inherit from T&" shouldn't have some other meaning. I think you and I would probably agree that "inherit from int" has a reasonably intuitive meaning, and "inherit from pointer-to-int" has a reasonably intuitive meaning; but do our intuitions diverge at "inherit from reference-to-int"?  Don't answer that. ;)

Here's another addition to the syntax bikeshed:

template<typename T> class Ref {
private:
    T *p;
public:
    public T&() { return *p; }
    public const T&() const { return *p; }
};

The advantage of this syntax (without anything explicitly in the class header) is that you can have const and non-const versions that give different results; for example, giving back an iterator in one case and a const_iterator in another. I have no use-cases in mind.
I think this would be confusing to anyone having programmed in java or C#. I would intuitively interpret it as a way to make an exception to the general rule in this part of the class head (like defining one public method in the private: section). I guess there may be some who think harmonizing with other languages and thus allow this would be a good idea.

I notice that you dropped the 'operator' keyword which makes it a bit odd. This could be a good thing as it stands out more.

I also had the idea to explore how this idea could be expressed without using the base class list. My take was to add a context sensitive keyword allowed after a conversion operator, which makes it more "powerful", defined as "behaves as if it inherited T":

    template<typename T> class Ref<T> {
    public:
        operator T&() implicit { return *m_ptr; }
    };

The choice of the word implicit is motivated by the fact that it can loosely be seen as the inverse of 'explicit' on a conversion operator. When explicit makes it harder to call (accidentally) implicit makes it easier, again: As if it was inheriting T. We could also select the word 'inherit' to make it perfectly clear how to interpret the result.

When pursuing this line of thought I started thinking about what the actual difference between inheritance and (non explicit) conversion operator to a reference is. After all both convert a reference to Ref<T> to a reference to T. There are two differences I could think of: 1) The cost of the conversion operator is higher, so that the compiler only does one of them. 2) the baseclass conversion is done for the "this" pointer when accessing members,which is not done for a conversion operator. I don't think there would be a difference for a template function parameter, in both cases Ref<T> would be chosen as no conversions are done. 

With concepts in the picture I guess this could change: Imagine a class A which has an operator B() and a template function with a concept C as parameter type. B fulfills the C concept but A doesn't.Would this function be callable with an A as it has a conversion operator to a type fulfilling a concept? My spontaneous feeling is that it should not, but for inheritance by reference in any form it would be  allowed as presumably a subclass fulfills all concepts that its baseclass fulfills (given public inheritance of course).

 
The "public" here is meant to indicate that it works kind of like public inheritance; however, I have no idea what "private" would mean in the same context, given that the access control of the function is actually controlled by the "public:" on the preceding line. P0352R1 has the exact same problem: it's unclear what would happen if you wrote

template<typename T> class Ref : public using T {
private:
    T *p;
    operator T&() { return *p; }  // this is a private conversion operator!
};

(Does this Ref<T> behave like a smart reference only in friend contexts?)
Or, as I described above, it could mean to only be usable for the "convert to baseclass" idiom, but not being callable directly. However, given the intermediate discussion: I don't see how a conversion operator would ever be called "directly" as the compiler would always find the convert-to-baseclass path easier when it today would find the conversion operator. Well you could do:
    
    Ref<int> x;
    x.operator int();

and then it would complain that it was private, but basically it seems that the public/private on the inheritance and the public/private on the conversion operator are redundant, which speaks for a solution without mentioning in the base class list as it would require extra rules for what happens if the public/private of the two places are not the same.

 

–Arthur

Mathias Gaunard

unread,
Jun 28, 2017, 6:22:43 AM6/28/17
to std-pr...@isocpp.org
On 28 June 2017 at 10:45, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:

operator auto (at least the meaning I put into that notion) is a way to define a type that an object of the type having the operator will be converted to in case it is assigned to an auto variable.
 
The primary use case is for expression templates where you typically would like to evaluate the expression when assigning it to an auto variable:

auto m = A * B;

As a big writer and user of expression templates libraries, I'm not sure you really want that.
In any case, the simplest way to do this IMO would be to just allow users to specialize std::decay, and to define auto in terms of it, but that's another topic...

Matthew Woehlke

unread,
Jun 28, 2017, 7:48:29 AM6/28/17
to std-pr...@isocpp.org, Nicol Bolas
On 2017-06-27 12:32, Nicol Bolas wrote:
> Stroustrup's P0700 (PDF) <http://wg21.link/P0700> paper is a critique of P0352:
> Smart References through Delegation (PDF) <http://wg21.link/P0352>.

(General observations)

If `operator.` *only* ever worked when an actual `.` appeared, I'd have
a somewhat harder time arguing against it. As much as it rubs me wrong
on an intuitive level, it *does* make *some* sense to compare it to
`operator->`. After all, the latter is also built-in for some types and
user defined for others.

Where this breaks down, however, is that I don't believe `->` is ever
defaulted for a user type. Either a type has built-in `->`, or it has
user-provided (or at worst, base-class-provided) `->`.

But then you throw in stuff like `++x` is supposed to call
`operator.`... wait, wtf?? What about `foo(x)`? Suddenly we are bending
Principle of Least Surprise into a pretzel.

Comparitively, P0352 leverages implicit conversions, so some of the
operations we want come from a feature that already exists. The rest
fall out from a slight tweaking of inheritence... the "proxy" is in
effect a subclass for lookup rules (something we have already), only the
implicit conversion when calling a "base class" member uses the provided
conversion operator rather than a built-in conversion (subclass to base
class). In fact, given that virtual inheritance can already have a
non-trivial form of this conversion, this isn't such a huge leap.

Oh, and... what about:

... Ref<X>::mem_fun(...)
{
foo(...); // Does this call Ref::foo or X::foo?
}

Historically, whether to write `this->` in front of member access has
been a matter of style. With P0352, it is "obvious" that it remains
guaranteed safe (i.e. will not change meaning) to remove `this->`. Is
that still the case with P0416?

That all said, TBH I have never felt the need for the feature either of
these proposals would permit...

> With P0416, operator-dot, I now have to remember a bunch of new lookup
> rules. I have to check to see if a type overloads operator-dot. If it does,
> then I have to know exactly what the new rules are, that the proxied type
> takes priority. But these rules are complicated. There are times when the
> proxied type doesn't take priority (if I recall correctly, in constructors
> and destructors). And more importantly, if I really want to access the
> proxy rather than the proxied type, I have to learn a complex new idiom:
> `std::addressof(x)->y`.
>
> The advantage of P0352 is that I *don't* have new rules to learn. `x.y`
> follows C++ lookup rules that have existed since the day C++ was
> standardized. The derived class overrides the base class names. That's the
> rule, and we all know it by heart. So the proxy takes priority. And if you
> want to ensure access to proxied members, we already have an idiom for
> that: `x.ProxiedType::y`. That's all well understood C++, and requires no
> extension to the rules to achieve.

Hear, hear.

On 2017-06-27 19:15, Arthur O'Dwyer wrote:
> One thing that I did take from Stroustrup's P0700, though, is that I'm
> still confused about what the desired behavior is if I have
>
> template<class T> void f(T t);
> void g(Ref<X> r);
> Ref<X> rx;
> f(rx); // deduce T=Ref<X> or just X?
> g(rx); // pass a copy of rx, or pass Ref<X>(X(rx))?
> auto r2 = rx; // deduce Ref<X> or just X?
>
> I would *hope* that the desired behavior is "the former, not the latter" in
> each case, but that seems too... reasonable? I mean, isn't the whole point
> of operator.() to create confusion about what is and what isn't a reference?

...and a nice aspect of P0352 is that it has *no effect* on any of the
above code. All of these behaviors would be controlled by the existence
of the implicit conversion operator, which is a feature we already have
today. Or, rather, they *wouldn't* be, because right now we get the
former behaviors, with or without the conversion operator being present
in `Ref`.

--
Matthew

Matthew Woehlke

unread,
Jun 28, 2017, 7:50:12 AM6/28/17
to std-pr...@isocpp.org, Nicol Bolas
On 2017-06-27 19:28, Nicol Bolas wrote:
>> One nice thing about P0352, that I might see a use for, is that it
>> apparently re-enables the ability to "inherit from" final classes. A few
>> nifty metaprogramming techniques went out the window with the introduction
>> of the final qualifier, because you could no longer use the "inherit from
>> user-defined T" trick to get T's members into the same scope as some other
>> member of your own devising. But with P0352, we can get all those
>> mechanisms back easily; final classes no longer have absolute power over
>> library writers.
>
> I don't think P0352 should allow that. If a class is declared `final`, it
> seems decidedly poor form for us to allow it to be used as a base class,
> even if it is not a base class subobject. I mean, you can decide that
> class-scoped `final` is a bad idea, but if we're going to have it, it
> should actually *work*.

If that were the case, it would be a *compelling* argument against
P0352. You can't very well have a "smart reference" / "proxy" type if
there is a mechanism to arbitrarily prevent it being used with some types.

That said... what is the use case for "final"? Maybe it's the Free
Software coder in me, but the idea of preventing someone extending your
class just rubs me wrong.

What's that? "Performance reasons", you say? Okay, I can buy that... but
therein lies the cool part; *allowing P0352 on `final` types should not
obviate the performance benefits of `final`*. Eat cake¹. Have cake.

(¹ The cake is not a lie ;-).)

--
Matthew

Matthew Woehlke

unread,
Jun 28, 2017, 8:07:21 AM6/28/17
to std-pr...@isocpp.org, Arthur O'Dwyer
On 2017-06-27 22:39, Arthur O'Dwyer wrote:
> Here's another addition to the syntax bikeshed:
> [...]

If we're going to bikeshed, what about:

template <typename X> class Ref {
X* m_p;
...
public:
using X::* -> { return *m_p; }
}

This says 'inherit all members of `X` as if it were a base class, and
when calling such a member, execute this code to obtain the reference to
`X`. The exact syntax is subject to further bikeshedding, but the
general idea is that there exists a public `using` declaration *in the
class body* which has the "conversion operator" code attached to it.

This could even be bolted on to P0352 as it currently stands:

template <typename X> class Ref : public using X {
X* m_p;
...
public:
using X -> { return *m_p; }
}

Here, the `using X ->` syntax must be associated with `using X` as a
"base class". The latter provides a "conversion operator" that is *only*
accessible when performing a conversion due to P0352 name lookup.

An alternative syntax for this "limited use conversion operator" could
be e.g. `operator using X&() { ... }`.

--
Matthew

Nicol Bolas

unread,
Jun 28, 2017, 9:30:06 AM6/28/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com


On Wednesday, June 28, 2017 at 7:50:12 AM UTC-4, Matthew Woehlke wrote:
On 2017-06-27 19:28, Nicol Bolas wrote:
>> One nice thing about P0352, that I might see a use for, is that it
>> apparently re-enables the ability to "inherit from" final classes. A few
>> nifty metaprogramming techniques went out the window with the introduction
>> of the final qualifier, because you could no longer use the "inherit from
>> user-defined T" trick to get T's members into the same scope as some other
>> member of your own devising. But with P0352, we can get all those
>> mechanisms back easily; final classes no longer have absolute power over
>> library writers.
>
> I don't think P0352 should allow that. If a class is declared `final`, it
> seems decidedly poor form for us to allow it to be used as a base class,
> even if it is not a base class subobject. I mean, you can decide that
> class-scoped `final` is a bad idea, but if we're going to have it, it
> should actually *work*.

If that were the case, it would be a *compelling* argument against
P0352. You can't very well have a "smart reference" / "proxy" type if
there is a mechanism to arbitrarily prevent it being used with some types.

Or we could just rip class-level `final` out of the language. I'm good with that. C++ is just not a language where class `final` makes sense; we use inheritance a lot for things that have nothing to do with traditional vtable polymorphism.

That said... what is the use case for "final"? Maybe it's the Free
Software coder in me, but the idea of preventing someone extending your
class just rubs me wrong.

What's that? "Performance reasons", you say?

The only performance benefit you could get is allowing the compiler to de-virtualize virtual functions on the type. And you could get the same effect by just saying that class-level `final` means that all virtual functions, inherited or not, are implicitly declared `final`.

Nicol Bolas

unread,
Jun 28, 2017, 9:40:25 AM6/28/17
to ISO C++ Standard - Future Proposals


On Tuesday, June 27, 2017 at 10:40:00 PM UTC-4, Arthur O'Dwyer wrote:
On Tue, Jun 27, 2017 at 5:05 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
The point of P0352 is to not have to define any new rules. So all the examples Stroustrup was confused about work as if Ref<T> actually inherited T in the original sense.

This means "the former, not the latter" in each case, except if the expresstion-template conversion-to-auto operator is in the Ref<X> API, which may be appropriate for some kinds of references.

FWIW, I think I'm vaguely aware of "operator auto", but I don't understand what expression templates have to do with it.

[...]
One point I agree with Stroustrup about is a bit of unease with the need for mentioning the refered type twice. Unfortunately my suggested syntax has the same flaw, except if use auto as the return type of the refered function:

template<typename T> class Ref : public T(get_ref) {
     T* m_ptr;
     auto get_ref() { return *m_ptr; }
};

So the syntax idea was to explicitly mention a named method in the baseclass list instead of (re)using the using keyword yet again.

Even without "auto", this has the same problem w.r.t. the repetition of the word "get_ref". Also, I don't instantly intuitively understand what should happen if "get_ref" is an inherited member, or if I inherit from Ref<T> and override "get_ref" — I suspect it all falls out naturally but for some reason "operator T&" doesn't cause that mental block for me.

The problem I have with trying to use a function other than `operator T&` to convert to the delegated "base class" is that it now makes it possible for people to create incoherence in their types. You provide some named delegate function to do the conversion, but you could also provide an implicit conversion to the same `T` which does a different thing than the delegate.

I don't want to have to guess how this works:

T &t = variable;

I don't want to have to wonder if the `variable` object is calling the delegate function or `operator T&`. I want to be able to see that it has an `operator T&` and be reasonably assured that is what it is calling.
 
One benefit of the "get_ref" approach is that "get_ref" could be a static member function, whereas I suppose "operator T&" cannot be static.

Well, we could always change that. Granted, I'm not sure why you'd want that.
 
Another benefit of the "get_ref" approach is that if we re-use "operator T&" for purposes of operator.(), then we're unnecessarily tying together the notions of "delegates-all-the-members-of-T-like-so" and "is-implicitly-convertible-to-T". I can easily imagine a "smart reference" type that doesn't want to be implicitly (nor explicitly) convertible to T&, just as I can easily imagine a "smart pointer" type that doesn't want to be implicitly (nor explicitly) convertible to T*.

I can't imagine that.

`weak_ptr` is the only "smart pointer" type I know that is not convertible to `T*`. But, `weak_ptr` also doesn't have `operator->` overloaded; you access the pointer through an entirely different API from most smart pointers (and for good reason).

So what kind of smart pointer type would you be talking about where `operator->` exists but not `operator T*`? I just don't think it makes sense to be able to do `ptr->MemberOfT` and not be able to do `T* pt = ptr; pt->MemberOfT;`.

Barry Revzin

unread,
Jun 28, 2017, 10:23:18 AM6/28/17
to ISO C++ Standard - Future Proposals

So what kind of smart pointer type would you be talking about where `operator->` exists but not `operator T*`? I just don't think it makes sense to be able to do `ptr->MemberOfT` and not be able to do `T* pt = ptr; pt->MemberOfT;`.

Maybe I'm misunderstanding, but neither std::unique_ptr<T> nor std::shared_ptr<T> have an operator T*.  

Nicol Bolas

unread,
Jun 28, 2017, 10:32:57 AM6/28/17
to ISO C++ Standard - Future Proposals
On Wednesday, June 28, 2017 at 10:23:18 AM UTC-4, Barry Revzin wrote:

So what kind of smart pointer type would you be talking about where `operator->` exists but not `operator T*`? I just don't think it makes sense to be able to do `ptr->MemberOfT` and not be able to do `T* pt = ptr; pt->MemberOfT;`.

Maybe I'm misunderstanding, but neither std::unique_ptr<T> nor std::shared_ptr<T> have an operator T*.  

I could have sworn they had implicit conversions to T*. I guess I've gotten so used to writing `.get()` that I don't even notice it...

Ville Voutilainen

unread,
Jun 28, 2017, 10:37:50 AM6/28/17
to ISO C++ Standard - Future Proposals
On 28 June 2017 at 03:05, Bengt Gustafsson
<bengt.gu...@beamways.com> wrote:
> The point of P0352 is to not have to define any new rules. So all the
> examples Stroustrup was confused about work as if Ref<T> actually inherited
> T in the original sense.


The problem I have with the using-inheritance is that while it covers
the conversion aspect in a familiar fashion
like inheritance does, it's otherwise not at all like inheritance.
There's no base subobject. I can't override virtuals.
I'm not sure whether I can delegate to the base, and if I can, I don't
know what sort of a conversion is now applied
to calls that previously just converted *this to the base type in an
invisible fashion. I guess the latter issue may be present
in both approaches. I have no trouble understanding an operator that
kicks in for call dispatch rather than for conversions.

My take on the suggestion that customizing dot-access needs a new kind
of inheritance to be introduced into the language
is to perhaps rather not have customizable dot-access at all.

Nicol Bolas

unread,
Jun 28, 2017, 10:58:10 AM6/28/17
to ISO C++ Standard - Future Proposals
On Wednesday, June 28, 2017 at 10:37:50 AM UTC-4, Ville Voutilainen wrote:
On 28 June 2017 at 03:05, Bengt Gustafsson
<bengt.gu...@beamways.com> wrote:
> The point of P0352 is to not have to define any new rules. So all the
> examples Stroustrup was confused about work as if Ref<T> actually inherited
> T in the original sense.


The problem I have with the using-inheritance is that while it covers
the conversion aspect in a familiar fashion
like inheritance does, it's otherwise not at all like inheritance.
There's no base subobject. I can't override virtuals.
I'm not sure whether I can delegate to the base, and if I can, I don't
know what sort of a conversion is now applied
to calls that previously just converted *this to the base type in an
invisible fashion. I guess the latter issue may be present
in both approaches. I have no trouble understanding an operator that
kicks in for call dispatch rather than for conversions.

I have a harder time understanding why `operator.()` is called when I don't actually use a dot anywhere. Like the `++ref` case.

With inheritance, I can at least see that type in the base class list. If it were a regular base class, I could follow the rules for how that `++` works. The only difference between that and this is where the object is stored/how the conversion takes place.
 
My take on the suggestion that customizing dot-access needs a new kind
of inheritance to be introduced into the language
is to perhaps rather not have customizable dot-access at all.

That's a pretty terrible way to look at a feature. You're basically saying "if it's not done the way I think it ought to be done, then we shouldn't do it at all."

The questions that matter to me are:

1) Does it solve a problem?
2) Does it solve a problem that needs to be solved?
3) Does it solve that problem in a way that makes sense with the rest of the language?
4) Does it actively break anything by existing?

It should also be noted that the "new kind of inheritance" is a general tool, rather than overloading operator-dot. We can find ways to use this kind of delegated inheritance in ways that you couldn't for operator-dot.

For example, consider a string class. You can give it the interface of `string_view` by having it derive from `string_view`. But that would require you to give it a real base class subobject, which would have to be kept up-to-date with the actual string data.

But if you use a delegated object, the `string_view` you return from the conversion operator could be a prvalue rather than a reference. You would generate the view as needed. You could also pick-and-choose which interfaces to export through private delegate inheritance and explicit `using` declarations (though I don't think that's part of P0325, but it would be a perfectly valid improvement).

That is, don't think of this feature as a way to get "operator-dot". Think of it as a general tool that solves the problems we want "operator-dot" for, but also potentially solves other problems.

Matthew Woehlke

unread,
Jun 28, 2017, 12:09:49 PM6/28/17
to std-pr...@isocpp.org, Nicol Bolas
On 2017-06-28 10:58, Nicol Bolas wrote:
> On Wednesday, June 28, 2017 at 10:37:50 AM UTC-4, Ville Voutilainen wrote:
>> I'm not sure whether I can delegate to the base, and if I can, I don't
>> know what sort of a conversion is now applied
>> to calls that previously just converted *this to the base type in an
>> invisible fashion.

The conversion is the `operator T&`, of course. This isn't *that* much
stranger than what happens with virtual base classes.

Why couldn't you delegate to the base? Do you mean something like:

template <typename T> class Ref
{
void foo()
{
...
this->T::foo();
}
}

...? This would work with P0352 AFAIUI.

>> My take on the suggestion that customizing dot-access needs a new
>> kind of inheritance to be introduced into the language is to
>> perhaps rather not have customizable dot-access at all.
>
> That's a pretty terrible way to look at a feature. You're basically saying
> "if it's not done the way I think it ought to be done, then we shouldn't do
> it at all."
>
> The questions that matter to me are:
>
> 1) Does it solve a problem?
> 2) Does it solve a problem that needs to be solved?

Personally, I'm not convinced that the answer to these is "yes".
However, I find the potential *alternate* uses of P0352 (e.g. see below)
more interesting.

> It should also be noted that the "new kind of inheritance" is a general
> tool, rather than overloading operator-dot. We can find ways to use this
> kind of delegated inheritance in ways that you couldn't for operator-dot.

...which is another reason why I have a fairly strong preference for
P0352 over P0416. (If, that is, we need either one of them ;-).)

> For example, consider a string class. You can give it the interface of
> `string_view` by having it derive from `string_view`.

Just going to say... yeah, I think there is some very cool potential
here. (I wonder if any Qt devs are listening; it would be interesting to
know if this might be useful to QString and friends...)

ISTR one of the rationale's for `operator.` is "proxy classes". For
these, I think the P0352 way of looking at the problem, and especially
the potential to provide *multiple interfaces*, actually makes much more
sense.

--
Matthew

Ville Voutilainen

unread,
Jun 28, 2017, 12:15:57 PM6/28/17
to ISO C++ Standard - Future Proposals
On 28 June 2017 at 17:58, Nicol Bolas <jmck...@gmail.com> wrote:
> On Wednesday, June 28, 2017 at 10:37:50 AM UTC-4, Ville Voutilainen wrote:
>>
>> On 28 June 2017 at 03:05, Bengt Gustafsson
>> <bengt.gu...@beamways.com> wrote:
>> > The point of P0352 is to not have to define any new rules. So all the
>> > examples Stroustrup was confused about work as if Ref<T> actually
>> > inherited
>> > T in the original sense.
>>
>>
>> The problem I have with the using-inheritance is that while it covers
>> the conversion aspect in a familiar fashion
>> like inheritance does, it's otherwise not at all like inheritance.
>> There's no base subobject. I can't override virtuals.
>> I'm not sure whether I can delegate to the base, and if I can, I don't
>> know what sort of a conversion is now applied
>> to calls that previously just converted *this to the base type in an
>> invisible fashion. I guess the latter issue may be present
>> in both approaches. I have no trouble understanding an operator that
>> kicks in for call dispatch rather than for conversions.
>
>
> I have a harder time understanding why `operator.()` is called when I don't
> actually use a dot anywhere. Like the `++ref` case.
>
> With inheritance, I can at least see that type in the base class list. If it
> were a regular base class, I could follow the rules for how that `++` works.
> The only difference between that and this is where the object is stored/how
> the conversion takes place.

Yes, and the other differences between inheritance and this
delegation-conversion
are the ones that make this proposal problematic for me.

>
>>
>> My take on the suggestion that customizing dot-access needs a new kind
>> of inheritance to be introduced into the language
>> is to perhaps rather not have customizable dot-access at all.
>
>
> That's a pretty terrible way to look at a feature. You're basically saying
> "if it's not done the way I think it ought to be done, then we shouldn't do
> it at all."

Well, what you wrote in quotes would probably be a terrible way to
look at a feature,
but since that's not what I said, I can point out that if it's done in
a way completely
unacceptable to me, I'm going to oppose it, with suggestions how to remove that
opposition.

> The questions that matter to me are:
>
> 1) Does it solve a problem?
> 2) Does it solve a problem that needs to be solved?
> 3) Does it solve that problem in a way that makes sense with the rest of the
> language?
> 4) Does it actively break anything by existing?

There are rather more questions that matter to me, like
5) does it clash with existing and fundamental language facilities,
and if it does
so, is it equally fundamental?

> It should also be noted that the "new kind of inheritance" is a general
> tool, rather than overloading operator-dot. We can find ways to use this
> kind of delegated inheritance in ways that you couldn't for operator-dot.

Which is not necessarily a benefit. I have sympathy with the confusion about
an operator-dot being invoked in expressions that have no dot-access in them,
that's not exactly a strong point for either of the proposed approaches. One of
them is a member access operator that also converts, the other is a
conversion that is also
used for member access. Neither of them allows me to completely
reflect an interface,
neither of them allows me to customize how an interface is reflected,
so just because
one of them can do more tricks than the other doesn't change that they
both fall short.

> For example, consider a string class. You can give it the interface of
> `string_view` by having it derive from `string_view`. But that would require
> you to give it a real base class subobject, which would have to be kept
> up-to-date with the actual string data.
> But if you use a delegated object, the `string_view` you return from the
> conversion operator could be a prvalue rather than a reference. You would
> generate the view as needed. You could also pick-and-choose which interfaces
> to export through private delegate inheritance and explicit `using`
> declarations (though I don't think that's part of P0325, but it would be a
> perfectly valid improvement).
> That is, don't think of this feature as a way to get "operator-dot". Think
> of it as a general tool that solves the problems we want "operator-dot" for,
> but also potentially solves other problems.

Well, on that last part I agree with completely. If we get a general
mechanism for reflecting
an interface of a type onto another type, it's more plausible to put
syntax for that mechanism
in the class-head. Neither of these proposed facilities get that far,
so that's another reason
why I'm saying that perhaps we shouldn't go for customizable
dot-access, and perhaps
we shouldn't go for this dot-access-plus-conversions feature either,
but try for something
more general.

Ville Voutilainen

unread,
Jun 28, 2017, 12:18:57 PM6/28/17
to ISO C++ Standard - Future Proposals
On 28 June 2017 at 19:09, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> On 2017-06-28 10:58, Nicol Bolas wrote:
>> On Wednesday, June 28, 2017 at 10:37:50 AM UTC-4, Ville Voutilainen wrote:
>>> I'm not sure whether I can delegate to the base, and if I can, I don't
>>> know what sort of a conversion is now applied
>>> to calls that previously just converted *this to the base type in an
>>> invisible fashion.
>
> The conversion is the `operator T&`, of course. This isn't *that* much
> stranger than what happens with virtual base classes.
>
> Why couldn't you delegate to the base? Do you mean something like:
>
> template <typename T> class Ref
> {
> void foo()
> {
> ...
> this->T::foo();
> }
> }
>
> ...? This would work with P0352 AFAIUI.

I'm talking about this:

struct X {void f();};

struct Y : using X
{
void g()
{
X::f();
}
};

What does that code do?

Barry Revzin

unread,
Jun 28, 2017, 12:25:29 PM6/28/17
to ISO C++ Standard - Future Proposals
One of 
them is a member access operator that also converts, the other is a 
conversion that is also 
used for member access.
 
I interpret 352 as these being independent things. The conversion function to reference isn't used for member lookup, it's just used to get the implicit subobject of the referring type to actually call the thing it finds through name lookup. With that interpretation in mind...

I'm talking about this:  
struct X {void f();};

struct Y : using X
{
    void g()
    {
        X::f();
    }
};

What does that code do?

I'd expect it to fail to compile with no conversion found from Y to X, necessary to actually call f().

Ville Voutilainen

unread,
Jun 28, 2017, 12:36:53 PM6/28/17
to ISO C++ Standard - Future Proposals
Oh indeed. So

struct Y : using X
{
X x;
operator X&() {return x;}
void g()
{
X::f();
}
};

Now? I guess it uses the conversion, and then calls Y::x.f()?

Matthew Woehlke

unread,
Jun 28, 2017, 12:37:30 PM6/28/17
to std-pr...@isocpp.org, Ville Voutilainen
On 2017-06-28 12:18, Ville Voutilainen wrote:
> I'm talking about this:
>
> struct X {void f();};
>
> struct Y : using X
> {
> void g()
> {
> X::f();
> }
> };
>
> What does that code do?

Fails to compile? ;-)

Assuming you implied that `operator X&` exists, obviously it does:

(this->operator X&()).f();

Ah, but *that* isn't completely obvious either. What if X::f is virtual?
(But I think P0416 has this same problem? Although I would assume that
P0416 "obviously" would apply virtual dispatch.)

That's probably a question that needs to be raised re: P0352.

--
Matthew

Nicol Bolas

unread,
Jun 28, 2017, 12:42:56 PM6/28/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com

I agree. I've personally never really saw the need for "smart references"; I have never had a problem with using `->` when I want to access the stored value and `.` when I want to access the wrapper. But the fact that P0352 provides a general tool that can be used for things besides just "smart references" gives it better motivation.

> It should also be noted that the "new kind of inheritance" is a general
> tool, rather than overloading operator-dot. We can find ways to use this
> kind of delegated inheritance in ways that you couldn't for operator-dot.

...which is another reason why I have a fairly strong preference for
P0352 over P0416. (If, that is, we need either one of them ;-).)

> For example, consider a string class. You can give it the interface of
> `string_view` by having it derive from `string_view`.

Just going to say... yeah, I think there is some very cool potential
here. (I wonder if any Qt devs are listening; it would be interesting to
know if this might be useful to QString and friends...)

ISTR one of the rationale's for `operator.` is "proxy classes". For
these, I think the P0352 way of looking at the problem, and especially
the potential to provide *multiple interfaces*, actually makes much more
sense.

Also, it's important to note that by using base class notation, we get a built-in way to disambiguate between the multiple interfaces where needed. If we relied on `operator.` to achieve the same effect, we would need to develop a new mechanism to accomplish that effect.

Ville Voutilainen

unread,
Jun 28, 2017, 12:46:09 PM6/28/17
to Matthew Woehlke, ISO C++ Standard - Future Proposals
Yep. There's been some talk about restricting using-bases and usual
bases so that one base
can't be both. I wonder whether things like this are the reason for
it. I don't have such restrictions
with the operator-dot proposal.

Envision something like this:

struct B {void f() {do_f();} virtual void do_f() {}};

struct X : B, using B
{
B b;
operator B&() {return b;}
void do_f() override;
void g() {B::f();} // who ya gonna call? Ghostbusters? Sure, it
might be ambiguous.
};

And no, I'm not entirely sure I understand what the operator-dot
proposal does in such cases either.

Nicol Bolas

unread,
Jun 28, 2017, 12:51:12 PM6/28/17
to ISO C++ Standard - Future Proposals, ville.vo...@gmail.com
On Wednesday, June 28, 2017 at 12:37:30 PM UTC-4, Matthew Woehlke wrote:
On 2017-06-28 12:18, Ville Voutilainen wrote:
> I'm talking about this:
>
> struct X {void f();};
>
> struct Y : using X
> {
>     void g()
>     {
>         X::f();
>     }
> };
>
> What does that code do?

Fails to compile? ;-)

Assuming you implied that `operator X&` exists, obviously it does:

  (this->operator X&()).f();

Ah, but *that* isn't completely obvious either. What if X::f is virtual?
(But I think P0416 has this same problem? Although I would assume that
P0416 "obviously" would apply virtual dispatch.)

I think it would be good to provide a more clear example of the question:

struct base { virtual void f(); };

struct derived : base { virtual void f() override; };

struct user : using base
{
  derived d
;
 
operator base&() {return d;}

 
void g()
 
{
   
base::f();
 
}
};

We know that if you do `some_user.f()`, we should call `derived::f` via virtual dispatch. But what exactly happens in `g`?

I think the only reasonable answer would be that it calls exactly what it would have called if `Y` had a base class subobject of type `base`. That is, it will make a non-virtual dispatch call to `base::f`.

The only thing delegation ought to change is how we get the object to invoke the operation on. Whether it does virtual or non-virtual dispatch and things like that should work exactly as if it were a regular base class of the type.

That's the whole idea of P0352 as a feature.

Nicol Bolas

unread,
Jun 28, 2017, 12:57:36 PM6/28/17
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Wednesday, June 28, 2017 at 12:46:09 PM UTC-4, Ville Voutilainen wrote:
Yep. There's been some talk about restricting using-bases and usual
bases so that one base
can't be both. I wonder whether things like this are the reason for
it. I don't have such restrictions
with the operator-dot proposal.

Envision something like this:

struct B {void f() {do_f();} virtual void do_f() {}};

struct X : B, using B
{
    B b;
    operator B&() {return b;}
    void do_f() override;
    void g() {B::f();} // who ya gonna call? Ghostbusters? Sure, it
might be ambiguous.
};

I'm fairly sure that should be forbidden, in the exact same way as we forbid a class from having two direct base classes of the same type. So if it would be broken if you took out `using`, then it should still be broken with `using`.

Nicol Bolas

unread,
Jun 28, 2017, 1:00:36 PM6/28/17
to ISO C++ Standard - Future Proposals

Yes, that's the idea. Anytime the standard would say that an expression refers to a base class subobject, it instead invokes the conversion operator appropriate to the base class's type to get the object to act on.

Barry Revzin

unread,
Jun 28, 2017, 1:00:40 PM6/28/17
to ISO C++ Standard - Future Proposals
Oh indeed. So

struct Y : using X
{
    X x;
    operator X&() {return x;}
    void g()
    {
        X::f();
    }
};

Now? I guess it uses the conversion, and then calls Y::x.f()?

Yeah, Y.operator X&().f()

struct B {void f() {do_f();} virtual void do_f() {}}; 
struct X : B, using B 

    B b; 
    operator B&() {return b;} 
    void do_f() override; 
    void g() {B::f();} // who ya gonna call? Ghostbusters? Sure, it 
might be ambiguous. 
}; 

Cracked up at the comment, nicely done! 

I'd expect it to be an error. Lookup on f() finds it in two different base classes, so we give up and call it ambiguous. Same reason that this is an error today:

struct A { void f(); };
struct B { void f(int ); };
struct C : A, B {
   
void g() { f(); }
};


 

Ville Voutilainen

unread,
Jun 28, 2017, 1:08:58 PM6/28/17
to ISO C++ Standard - Future Proposals
On 28 June 2017 at 19:57, Nicol Bolas <jmck...@gmail.com> wrote:
> On Wednesday, June 28, 2017 at 12:46:09 PM UTC-4, Ville Voutilainen wrote:
>>
>> Yep. There's been some talk about restricting using-bases and usual
>> bases so that one base
>> can't be both. I wonder whether things like this are the reason for
>> it. I don't have such restrictions
>> with the operator-dot proposal.
>>
>> Envision something like this:
>>
>> struct B {void f() {do_f();} virtual void do_f() {}};
>>
>> struct X : B, using B
>> {
>> B b;
>> operator B&() {return b;}
>> void do_f() override;
>> void g() {B::f();} // who ya gonna call? Ghostbusters? Sure, it
>> might be ambiguous.
>> };
>
>
> I'm fairly sure that should be forbidden, in the exact same way as we forbid
> a class from having two direct base classes of the same type. So if it would
> be broken if you took out `using`, then it should still be broken with
> `using`.


Right. The other proposal places no restrictions on what I can derive from.

Nicol Bolas

unread,
Jun 28, 2017, 1:19:35 PM6/28/17
to ISO C++ Standard - Future Proposals

Base classes represent an "is-a" relationship. Why would you need to proxy yourself?

It's a restriction to note. I just don't think it will matter in practice.

Matthew Woehlke

unread,
Jun 28, 2017, 1:24:05 PM6/28/17
to std-pr...@isocpp.org, Nicol Bolas
Are you sure? I could imagine there may exist some case where the
compiler knowing that a `Final*` is most definitely *really* a `Final*`
and not a `MoreDerived*` is helpful.

If nothing else, it would allow constexpr evaluation of a dynamic_cast.
(Presumably this would have to occur in template code, that the coder
didn't just use a static_cast in the first place.)

(That said, I tend to agree with you that I could live with the feature
being totally removed...)

--
Matthew

Matthew Woehlke

unread,
Jun 28, 2017, 1:38:15 PM6/28/17
to std-pr...@isocpp.org, Ville Voutilainen
On 2017-06-28 12:46, Ville Voutilainen wrote:
> On 28 June 2017 at 19:37, Matthew Woehlke wrote:
>> Ah, but *that* isn't completely obvious either. What if X::f is virtual?
>> (But I think P0416 has this same problem? Although I would assume that
>> P0416 "obviously" would apply virtual dispatch.)
>>
>> That's probably a question that needs to be raised re: P0352.
>
> Yep. There's been some talk about restricting using-bases and usual
> bases so that one base
> can't be both. I wonder whether things like this are the reason for
> it. I don't have such restrictions
> with the operator-dot proposal.

I don't think we're talking about the same problem. I'm talking about:

struct A { virtual void foo(); }
struct B : A { virtual void foo(); }

class R : using A
{
A* a;
public:
R(A* r) : a{r} {}
operator A&() { return *a; }
void foo() { A::foo(); }
}

auto r = R{new B};
r.foo();

Now... does that call A::foo or B::foo? Given similar P0416 code, I
would certainly expect it calls B::foo! It gets a little funky with the
P0352 example, above, because you wrote `A::foo`. In the P0352 usage,
that means 'delegate to the pseudo-base', but traditionally it *also*
means to stop virtual dispatch at `A`. Does it, or does it not, still
have that meaning in this case?

I don't know. Conceivably we want to allow both (which we certainly can
by requiring the user to explicitly invoke the conversion operator), but
it's an open question.

--
Matthew

Matthew Woehlke

unread,
Jun 28, 2017, 1:40:15 PM6/28/17
to std-pr...@isocpp.org, Ville Voutilainen
On 2017-06-28 13:08, Ville Voutilainen wrote:
I'm not sure that's a *feature*. Either way (P0352 / P0416) you've
created a mess. P0352 makes it easy and obvious to make that ill-formed.

--
Matthew

Nicol Bolas

unread,
Jun 28, 2017, 1:42:13 PM6/28/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com


On Wednesday, June 28, 2017 at 1:24:05 PM UTC-4, Matthew Woehlke wrote:
On 2017-06-28 09:30, Nicol Bolas wrote:
> On Wednesday, June 28, 2017 at 7:50:12 AM UTC-4, Matthew Woehlke wrote:
>> What's that? "Performance reasons", you say?
>
> The only performance benefit you could get is allowing the compiler to
> de-virtualize virtual functions on the type. And you could get the same
> effect by just saying that class-level `final` means that all virtual
> functions, inherited or not, are implicitly declared `final`.

Are you sure? I could imagine there may exist some case where the
compiler knowing that a `Final*` is most definitely *really* a `Final*`
and not a `MoreDerived*` is helpful.

There's at least one other case: if the class uses virtual inheritance, the compiler can turn accesses to virtual base classes into static offsets.

So let's recoup that. If a class is declared `final`, then:

1) All inherited virtual members are implicitly declared `final`.
2) Classes which derive from the `final` class may not `virtual`ly inherit from any base classes that the `final` class `virtual`ly inherits from.

Nicol Bolas

unread,
Jun 28, 2017, 1:55:04 PM6/28/17
to ISO C++ Standard - Future Proposals, ville.vo...@gmail.com

Perhaps we can allow `A::virtual foo` or some such to mean that we want to allow virtual dispatch, if `foo` is a virtual function.

Ville Voutilainen

unread,
Jun 28, 2017, 2:27:10 PM6/28/17
to ISO C++ Standard - Future Proposals
On 28 June 2017 at 20:19, Nicol Bolas <jmck...@gmail.com> wrote:
> Base classes represent an "is-a" relationship. Why would you need to proxy
> yourself?

That's a funny question. Fairly many proxies look like this:

struct DaInterface
{
virtual void mah_bucketz() = 0;
};

struct DaProxy : DaInterface
{
DaInterface* proxy_target;
void mah_bucketz()
{
// preamble code
proxy_target->mah_bucketz();
// post-amble code
}
};

I guess I could turn that inheritance into delegation, except I just
lost the capability to override, since a using-base
doesn't do that. The operator-dot proposal isn't all that suitable for
this either, because for multiple interface functions,
I might have different preambles and post-ambles so the operator dot
is not the place to write those in.

But perhaps proxies aren't what these proposals are for.

Jens Maurer

unread,
Jun 28, 2017, 4:43:33 PM6/28/17
to std-pr...@isocpp.org
On 06/28/2017 01:48 PM, Matthew Woehlke wrote:
> Historically, whether to write `this->` in front of member access has
> been a matter of style.

Not quite: If you want to name a member from a dependent base class,
a plain unqualified-id doesn't work, so "this->x" is one (common)
way to make the id-expression dependent.

Jens

Ville Voutilainen

unread,
Jun 28, 2017, 4:47:01 PM6/28/17
to Matthew Woehlke, ISO C++ Standard - Future Proposals
On 28 June 2017 at 20:40, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
>> Right. The other proposal places no restrictions on what I can derive from.
>
> I'm not sure that's a *feature*. Either way (P0352 / P0416) you've
> created a mess. P0352 makes it easy and obvious to make that ill-formed.


There's a very good chance that what you call a "mess" is exactly the
right solution to the problem at hand, though.

Nicol Bolas

unread,
Jun 28, 2017, 5:03:36 PM6/28/17
to ISO C++ Standard - Future Proposals
On Wednesday, June 28, 2017 at 2:27:10 PM UTC-4, Ville Voutilainen wrote:
On 28 June 2017 at 20:19, Nicol Bolas <jmck...@gmail.com> wrote:
> Base classes represent an "is-a" relationship. Why would you need to proxy
> yourself?

That's a funny question. Fairly many proxies look like this:

When I said "proxy", I was referring to the general idea of an object taking on the interface and general behavior of another. The standard problem operator-dot is intended to solve is "smart reference" proxies.

My question therefore is why would a smart reference type need to be a smart reference to one of its base classes? If you're using this for proxy iterators, why would you be deriving from the type you're proxying?

Jens Maurer

unread,
Jun 28, 2017, 5:15:20 PM6/28/17
to std-pr...@isocpp.org
On 06/28/2017 06:15 PM, Ville Voutilainen wrote:
> On 28 June 2017 at 17:58, Nicol Bolas <jmck...@gmail.com> wrote:

>> The questions that matter to me are:
>>
>> 1) Does it solve a problem?
>> 2) Does it solve a problem that needs to be solved?
>> 3) Does it solve that problem in a way that makes sense with the rest of the
>> language?
>> 4) Does it actively break anything by existing?
>
> There are rather more questions that matter to me, like
> 5) does it clash with existing and fundamental language facilities,
> and if it does
> so, is it equally fundamental?

Well, there's also "is the machinery we're introducing to
solve the problem worth it?"

I find the P0252R2 / P0416R1 machinery too involved for this
fringe feature.

(In case someone cares, I feel the same about the Coroutines TS,
although the feature is more broadly useful.)

And if we don't get consensus for either of the smart-references
proposals, life will go on. Very much so. I'd rather spend
WG21 committee time on concepts than on smart references.

Jens

Ville Voutilainen

unread,
Jun 28, 2017, 5:15:43 PM6/28/17
to ISO C++ Standard - Future Proposals
I don't know. But I find it plausible that I might well want to derive
from the same type I'm delegating to,
so not turning that possibility off seems like a plus for me. I have
no intention to write proxy iterators,
but I do think it quite likely that I'll use an operator-dot in types
other than what its motivation suggests.

Ville Voutilainen

unread,
Jun 28, 2017, 5:17:01 PM6/28/17
to ISO C++ Standard - Future Proposals
On 29 June 2017 at 00:15, Jens Maurer <Jens....@gmx.net> wrote:
> And if we don't get consensus for either of the smart-references
> proposals, life will go on. Very much so. I'd rather spend
> WG21 committee time on concepts than on smart references.


On that part I can easily grant your wish, because I have no plans to
discuss smart references
in Toronto.

Arthur O'Dwyer

unread,
Jun 28, 2017, 5:18:46 PM6/28/17
to ISO C++ Standard - Future Proposals
On Wed, Jun 28, 2017 at 11:27 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 28 June 2017 at 20:19, Nicol Bolas <jmck...@gmail.com> wrote:
> Base classes represent an "is-a" relationship. Why would you need to proxy yourself?

That's a funny question. Fairly many proxies look like this:

struct DaInterface
{
    virtual void mah_bucketz() = 0;
};

struct DaProxy : DaInterface
{
    DaInterface* proxy_target;
    void mah_bucketz()
    {
        // preamble code
        proxy_target->mah_bucketz();
        // post-amble code
    }
};

This is a very interesting example!  However, I think you're right that delegation can't be used for this purpose. I mean, my initial reaction was to rewrite this code (using P0352 syntax for delegation) like this:

struct DaInterface {
    virtual void mah_bucketz() = 0;
};
struct DaReality {
    void mah_buckets() override { puts("welcome to reality"); }
};
struct DaProxy : using DaInterface {
    DaInterface *proxy_target;
    operator DaInterface&() { return proxy_target; }
    operator const DaInterface&() const { return proxy_target; }
};

However, I see the problem with this — Because DaProxy does not layout-inherit from DaInterface, DaProxy IS-NOT-A DaInterface; it does not have a vptr. And therefore the following won't work:

void use(DaInterface *p) {
    p->mah_bucketz();  // access the virtual method
}
int main() {
    DaProxy x { new DaReality };
    use(&x);  // DOES NOT WORK! because x IS-NOT-A DaInterface at all
}

Furthermore, there is nowhere to plug in that "preamble code" and "postamble code" that Ville wants in DaProxy.
And this is because {P0416, P0352} are mechanisms to delegate from a certain (more or less dotted) syntax into the behaviors of some existing class. It's not a mechanism for creating new behaviors out of whole cloth. And "do this, but with a preamble" is a new behavior.

I think Ville has a cool problem here, and a realistic use-case. I just don't see any suitably elegant way to tell the code-generator, "I want you to give me a whole set of virtual methods just like my parent, but with this preamble and this postamble."  It's kind of like the way the compiler generates thunks around parent virtuals, but this is at a whole new level that feels like it would need lots of new heavyweight syntax.

–Arthur

Ville Voutilainen

unread,
Jun 28, 2017, 5:24:22 PM6/28/17
to ISO C++ Standard - Future Proposals
On 29 June 2017 at 00:18, Arthur O'Dwyer <arthur....@gmail.com> wrote:
> I think Ville has a cool problem here, and a realistic use-case. I just
> don't see any suitably elegant way to tell the code-generator, "I want you
> to give me a whole set of virtual methods just like my parent, but with this
> preamble and this postamble." It's kind of like the way the compiler
> generates thunks around parent virtuals, but this is at a whole new level
> that feels like it would need lots of new heavyweight syntax.


There are reflection-related proposals that go into such directions.
One example of those is
this one: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0707r0.pdf

I have had some discussions about the drafts of that proposal, and
pointed out that it can
do what operator-dot (or the conversion-delegation) can do. The
response to that was along the lines of "oh.. interesting". :)

I think I want a programmable call dispatch mechanism. With powerful
enough reflection, I can
get it. And I am not constrained by the hard-coded decisions that the
competing operator-dot proposals need to make.

Bryce Glover

unread,
Jun 28, 2017, 6:26:46 PM6/28/17
to std-pr...@isocpp.org
     (Pops head up from list-lurking.)  Reflection and virtual concepts, maybe?  

Returning to lurking, 
     Bryce Glover

Faisal Vali

unread,
Jun 28, 2017, 8:37:23 PM6/28/17
to <std-proposals@isocpp.org>
On Wed, Jun 28, 2017 at 4:15 PM, Jens Maurer <Jens....@gmx.net> wrote:

<snip>

> And if we don't get consensus for either of the smart-references
> proposals, life will go on. Very much so. I'd rather spend
> WG21 committee time on concepts than on smart references.
>

+1

p.s. While there are reasonably coherent answers to most questions
raised in this thread about the delegate/adapter proposal that would
be close enough to some (and not so to others) to the principle of
least surprise for even some of the more complicated
examples/questions on this thread (using what most of us know about
name-lookup, const-and-value-category based overloading of implicit
object parameters, conversion operators and the new rules from the
proposal) - in the end it might just come down to taste and commitment
- and given that this appears to already be a divisive topic with
strong folks claiming conclusive opinions on either side - (I should
admit that though a co-author of the adapter proposal, I would
characterize my opinion as malleable - I cared enough to co-author a
respectful proposal that we thought could give rise to a compromise
that might increase consensus) - but given the response, now I too
worry about embroiling the committee in an opinionated discussion that
could take chunks of time away from progress on far more influential
features (such as concepts) - but whether the committee is steered
into those waters is more Ville's burden to bear than mine ;)

p.s. If we really want true (compile-time or run-time) intercession or
aspects - I also remain convinced that neither of these would be the
best approach to solving those problems.

Bengt Gustafsson

unread,
Jul 7, 2017, 6:43:52 PM7/7/17
to ISO C++ Standard - Future Proposals


struct DaInterface
{
    virtual void mah_bucketz() = 0;
};

struct DaProxy : DaInterface
{
    DaInterface* proxy_target;
    void mah_bucketz()
    {
        // preamble code
        proxy_target->mah_bucketz();
        // post-amble code
    }
};

This is a very interesting example!  However, I think you're right that delegation can't be used for this purpose. I mean, my initial reaction was to rewrite this code (using P0352 syntax for delegation) like this:

struct DaInterface {
    virtual void mah_bucketz() = 0;
};
struct DaReality {
    void mah_buckets() override { puts("welcome to reality"); }
};
struct DaProxy : using DaInterface {
    DaInterface *proxy_target;
    operator DaInterface&() { return proxy_target; }
    operator const DaInterface&() const { return proxy_target; }
};

However, I see the problem with this — Because DaProxy does not layout-inherit from DaInterface, DaProxy IS-NOT-A DaInterface; it does not have a vptr. And therefore the following won't work:

void use(DaInterface *p) {
    p->mah_bucketz();  // access the virtual method
}
int main() {
    DaProxy x { new DaReality };
    use(&x);  // DOES NOT WORK! because x IS-NOT-A DaInterface at all
}
As it stands this does not compile, but if you want your proxy to be more transparent you could add an operator&() to it, returning the DaInterface* it contains. I think the call by reference case may be more interesting:

void use(DaInterface& r) { r.mah_bucketz(); }

DaProxy x { new DaReality; }

use(x);

Nah, this obviously calls the conversion operator (even now).

It would be possible to allow overriding virtual methods from the using base to get pre- and postamble code, creating a vtable where all non-overridden methods are thunked with a function calling the operator Base() and then the baseclass method (using virtual dispatch) but inside the use(DaInterface&) function those overridden functions would not be called anyway as the proxy has been dereffed already. This could be very confusing if you forget what using bases are.

I don't think any of this invalidates the mechanism as such, but I think it should be sold as a "rewrite rule" rather than a real inheritance. This speaks against using the baseclass list for this purpose and instead for annotation of the cast operator itself, such as by my previously suggested:

class DaProxy {
    operator DaInterface&() implicit;
}

Here the proxy obviously does not inherit the interface, but the word implcit means that when using a DaProxy object conversions should be as-if it was inheriting DaInterface (rewrite rule, sort of).

 

Furthermore, there is nowhere to plug in that "preamble code" and "postamble code" that Ville wants in DaProxy.
And this is because {P0416, P0352} are mechanisms to delegate from a certain (more or less dotted) syntax into the behaviors of some existing class. It's not a mechanism for creating new behaviors out of whole cloth. And "do this, but with a preamble" is a new behavior.

I think Ville has a cool problem here, and a realistic use-case. I just don't see any suitably elegant way to tell the code-generator, "I want you to give me a whole set of virtual methods just like my parent, but with this preamble and this postamble."  It's kind of like the way the compiler generates thunks around parent virtuals, but this is at a whole new level that feels like it would need lots of new heavyweight syntax.

I guess this is what P0707 could offer (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0707r0.pdf) together with the compile time reflection it relies on. The entire operator.() problem could be solved by P0707 given the right detailed design, I would guess. If not I think that detailed design would be flawed.
 

–Arthur

joseph....@gmail.com

unread,
Jul 20, 2017, 7:39:27 AM7/20/17
to ISO C++ Standard - Future Proposals

On Saturday, 8 July 2017 00:43:52 UTC+2, Bengt Gustafsson wrote:
class DaProxy {
    operator DaInterface&() implicit;
}

Here the proxy obviously does not inherit the interface, but the word implcit means that when using a DaProxy object conversions should be as-if it was inheriting DaInterface (rewrite rule, sort of).

I experimented with such a concept a while back (I called them auto conversions since implicit conversion is already a thing). I started with the premise that "smart references" should behave exactly like regular references as far as is possible. For example:

T v1;
ref<T> r1 = v1;
auto v2 = r1; // decltype(v2) is T
ref<auto> r2 = v1; // decltype(r2) is ref<T>
ref<auto> r3 = r1; // decltype(r3) is ref<T>

The problem is that it requires adding a whole bunch of new rules for type deduction and conversion, which is definitely not a trivial matter. That said, I think this would be the most promising approach because it more accurately reflects how references behave, in that T& does not inherit from T, but T& can convert to and from T (does the delegation proposal support smart references to non-class types?)

However, while the feature would be useful for implementing "interface adapters" such as propagate_const, I question whether it is really worth introducing such additional complexity into the language in any of the proposed forms. There are probably more important things to be working on.

Bengt Gustafsson

unread,
Jul 21, 2017, 2:47:15 AM7/21/17
to ISO C++ Standard - Future Proposals, joseph....@gmail.com


Den torsdag 20 juli 2017 kl. 13:39:27 UTC+2 skrev joseph....@gmail.com:

On Saturday, 8 July 2017 00:43:52 UTC+2, Bengt Gustafsson wrote:
class DaProxy {
    operator DaInterface&() implicit;
}

Here the proxy obviously does not inherit the interface, but the word implcit means that when using a DaProxy object conversions should be as-if it was inheriting DaInterface (rewrite rule, sort of).

I experimented with such a concept a while back (I called them auto conversions since implicit conversion is already a thing). I started with the premise that "smart references" should behave exactly like regular references as far as is possible. For example:

T v1;
ref<T> r1 = v1;
auto v2 = r1; // decltype(v2) is T
ref<auto> r2 = v1; // decltype(r2) is ref<T>
ref<auto> r3 = r1; // decltype(r3) is ref<T>

The problem is that it requires adding a whole bunch of new rules for type deduction and conversion, which is definitely not a trivial matter. That said, I think this would be the most promising approach because it more accurately reflects how references behave, in that T& does not inherit from T, but T& can convert to and from T (does the delegation proposal support smart references to non-class types?)
 
Avoiding having to write new rules is a primary reason to view this as indirect inheritance, regardless of the syntax. Just like smart pointers only mimic regular pointers to a certain level, smart references can (and should) mimic regular references only to a certain level. Basically what we want to do is to put a dot after the name of a smart reference and refer to its members and methods, but it could also be of interest to let conversion to T& be as cheap as if it was a base class.

If the syntax is related to a cast operator there is no reason to exclude basic types, but as the main purpose is the dot and basic types don't have members or methods this may not be all that important (compared to what we already get with the current cast operators). The only visible difference I can think of would be in a call to a function which takes an object of a type with a non-explicit constructor from the basic type, where this would currently not compile as there would be two user defined conversions chained:

class A {
    A(int) {}
};

class ref {
    operator int() { return 0; }
};

void fun(A a);

fun(1);    // works

ref x;

fun(x);   // Error: Can't chain cast operator and constructor-conversion.


Being able to call fun with x does not seem like a pressing need (and non-explicit one parameter ctors are discouraged anyway).

For user defined types there is another biggie which is the rules for overloading of methods, which is where the analogy with inheritance is important: If a method or member can be found in the subclass (or smart reference) it is selected, but if none is found the baseclass (or refered object) is consulted. The good thing about viewing smart references as inheriting is that the rules for these situations are already in place and well known for the inheritance case, apart from being logical and suitable for a smart reference.

One thing that has not been covered in this thread is assignment operators. However, it seems that the non-inheritance of assignment operators from base classes rule is exactly what we want for smart references too, so there is no additional rules to write for this either. Some more musing over the implications of this may be needed though, to see if there are some non-obvious cases where the inheritance rules may not be suitable, for instance for move or for the other assignment operators like operator+=().


However, while the feature would be useful for implementing "interface adapters" such as propagate_const, I question whether it is really worth introducing such additional complexity into the language in any of the proposed forms. There are probably more important things to be working on.

In the process of C++ standardization it is up to those who do the work to define the priorities. If I or someone else are interested (as were the authors of P0352) then it is obviously interesting enough. Given the large interest there has been in operator.() proposals I don't think this is a minor thing.

Finally a word about syntax. I'm personally very uncertain of which I like better between an annotated inheritance combined with a regular cast operator or an annotated cast operator and nothing in the base class list. One new idea I got was to call this _explicit_ inheritance, which would mean (of course) that the current type of inheritance is implicit, in the sense that the location of the base class object is implicitly known, while for an explicit inheritance the implementor of the subclass must provide a means to convert the object to its baseclass using a cast operator. The drawback with this idea is that putting _explicit_ on the cast operator itself is already allowed and has another meaning. Other than that I think it is easier to understand than _using_ in the base class list.
Reply all
Reply to author
Forward
0 new messages