--
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.
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.
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.
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.
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.
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 havetemplate<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.
–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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/35d2f989-1938-4288-89f4-114985039272%40isocpp.org.
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.
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&() { ... }};
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 wrotetemplate<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
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;
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?
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.
T &t = variable;
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*.
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;`.
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*.
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.
One of
them is a member access operator that also converts, the other is a
conversion that is also
used for member access.
I'm talking about this:
struct X {void f();};
struct Y : using X
{
void g()
{
X::f();
}
};
What does that code do?
> 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.
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.)
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();
}
};
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.
};
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()?
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.
};
struct A { void f(); };
struct B { void f(int ); };
struct C : A, B {
void g() { f(); }
};
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.
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:
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
}
};
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
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).
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>
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.