From: David Barrett-Lennard on 22 Apr 2010 08:20 On Apr 22, 9:08 pm, Seungbeom Kim <musip...(a)bawi.org> wrote: > On 2010-04-21 05:13, David Barrett-Lennard wrote: > > > > Yes, it seems that C++ programmers reserve the term "inheritance" for > > when subclassing is involved. > > Inheritance is a relationship between implementation, and not to be > confused with subtyping, which is a relationship between types. (And > to add confusion, subclasses and superclasses (not subtypes) describe > the inheritance relationship.) However, in C++ (public) inheritance > is the only way to achieve subtyping, so the two terms often get used > interchangeably. > > In the OP's example, the two types don't share any implementation, > so they're not in an inheritance relationship, but it seems that > they are meant to be in a subtyping relationship (through implicit > conversion). I thought you said in C++ public inheritance was the only way to achieve subtyping! I think it is important to understand the fundamental dichotomy here. A crucial point is that there are two entirely different notions of "type": 1) value-types 2) state-machine-types Therefore there are two very different and incompatible notions of subtype. For example if one interprets "type" as meaning a kind of state machine then one deduces that a circle is not an ellipse (because they are variables). If one interprets "type" as meaning a kind of abstract value then one deduces that a circle is an ellipse. IMO programmers who only think about kinds-of-state-machine miss out on something very important. Also ISTM C++ has been well designed for state-machine-types but less well designed for supporting value- types. E.g. why aren't implicit conversions transitive? Why don't const methods allow implicit conversions on *this? Why isn't there direct support for tagged union types? http://en.wikipedia.org/wiki/Sum_type -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Martin B. on 20 Apr 2010 15:46 On 19.04.2010 14:04, David Barrett-Lennard wrote: > Consider that we implement simple datatypes (i.e. simple "value > types") without virtual functions and all read only functions are > global functions (instead of class member functions). Let single > argument constructors be used to support implicit type conversions. > E.g. to emulate Square value is-a Rect value. > Well. Except that it "isn't" - not in the sense that we are supposed to(?) understand is-a, namely that everything (interface-wise) you can *do* with one thing has to be doable with the other thing. (in your example the interface of Rect implicitly contains the possibility to change w and h, which Square doesn't.) However, I think your example cleverly captures is-representable-by - or rather you capture, not that a Square "is-a" Rect, but that a Square can be (losslessly) converted to a Rect. > The following uses structs (public member variables) without > suggesting that is necessarily appropriate. > > struct Square { int s; }; > int side(Square sq) { return sq.s; } > > struct Rect > { > Rect(Square s) : w(side(s)), h(side(s)) {} > int w, h; > }; > int width(Rect r) { return r.w; } > int height(Rect r) { return r.h; } > int area(Rect r) { return width(r) * height(r); } > > void Test() > { > Square s = {10}; > int A = area(s); > } > > The width, height and area functions defined on rectangle values are > also available for square values, which is just what we need, and > could be viewed as a form of inheritance. (...) It could. With the right audience. I fear the average listener might be slightly confused though. I think the example shows that there can be value in defining "interface" functions such that if they only operate on public data there may be arguments for making them free functions rather than member functions. > Square inherits functions defined on rectangles but not the > implementation of rectangle (i.e. the member variables). (...) While I would agree that on a certain conceptual level "inheritance" seems an appropriate term, I also think that it already has a predefined meaning that doesn't quite fit your example. > Questions: > > 1) Is this a reasonable technique? > It looks neat and I'm sure there are a few cases where it is very reasonable. I just wouldn't call it inheritance. > 2) Is there a reason why C++ was designed so that const member > functions defeat the implicit conversions on *this? I'm sure I don't know what you mean by that?? cheers, Martin -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: David Barrett-Lennard on 22 Apr 2010 22:29 On Apr 23, 12:55 pm, �� Tiib <oot...(a)hot.ee> wrote: > On 23 apr, 02:18, David Barrett-Lennard <davi...(a)iinet.net.au> wrote: > > > On Apr 22, 6:59 pm, �� Tiib <oot...(a)hot.ee> wrote: > > > I avoid it. In large project such implicit conversions cause sometimes > > > hard to discover bugs because they hide simple typos. > > > Implicit conversions can be classified as safe or dangerous depending > > on whether there are surprises. I believe I gave a safe one that is > > very unlikely to be the source of bugs. Indeed all things being > > equal, more concise code tends to have fewer bugs. If you think > > otherwise I would appreciate a realistic example. > > Ok. Imagine function parameter #3 of 6 is Rect, but you pass > innocently Squares to it (from some array) in cycle of 1000 that is in > cycle of 1000. Only profiler shows that there takes place million of > Rect constructions and simple optimization can get rid of it. Bare eye > does not see it in noteworthy project. That's not a bug though. You seem to be saying that a general purpose algorithm shouldn't be implicitly available for special cases because it may be suboptimal. I find that idea highly questionable, particularly when it is easy to specialise the algorithm as and when profiling indicates that it is appropriate to do so. > > > > 2) Is there a reason why C++ was designed so that const member > > > > functions defeat the implicit conversions on *this? > > > > I don't think that they do. I think that your own const-correctness > > > and pass-by-value defects defeat it. Try if it works: > > > Where was there a const-correctness defect? > > There were no const correctness defects in posted code. There was no > code at all posted with const member functions. When *this does not > convert then const correctness bugs often cause it. So i said it about > imaginable code where you have that defeated conversion. Ok I understand and agree const correctness defects can defeat implicit conversions. However that is unrelated to the point I was making. > > Pass by value is hardly a "defect" (at least in the code I presented). > > Depends. For me there are some things that i imagine as values and > other things that i imagine as objects. Objects i pass rarely by value > (and if i do then i comment there why). Values i pass rarely by > reference (and again if so then i comment there why). What is optimal > from performance standpoint may be different, but the idiom what i > described is optimal on majority of cases. "Rect" felt like example of > object class. Had it been "Duration" then it felt like example of > value class. I'm unsure what you mean by "object". You seem to imply that objects are not values and yet it could be possible (albeit "rarely") to pass an object by value. I don't know what that means. I tend to avoid the word "object" because it is a source of confusion. I consider functions to support pass by value, or else pass by reference to a variable or state machine. I consider pass by const reference to mean passing a reference to a variable or state- machine that must be considered immutable. You mentioned passing a value by reference. Strictly speaking only a variable (or state machine) can be passed by reference. It just so happens that (apart from *this) C++ will happily create a temporary variable so that it appears like a value can be passed by const reference (and quite often proceed to optimise the temporary out of existence when in-lining). A Rect is certainly meant to be just a value-type. I have no idea why you would think otherwise. A Rect variable is hardly an interesting type of state machine because assignment allows for arbitrary state transitions. > > Actually I meant that a const member function Rect::area() cannot > > allow implicit conversions when attempting to use it to find the area > > of a Square. > > You want Square to behave like it had such member function that it > (and its base classes) actually lack? Then i misunderstood your > complaint. It is so in C++. Reason is again that if it did then it did > hide even more typos. C++ also does not let you to use chains of > implicit conversions. So if Square can be converted to Rect and Rect > to Polygon then that does not mean that you can use Square object as > parameter to functions that accept Polygon. I see no good reason to define Rect::area(), the purpose of which is to calculate areas of rectangle values, and not allow it to be used implicitly for square values. That it can't looks like a fault with C+ +. I remarked in a reply to Keith that it's a shame that implicit conversions aren't transitive. I find it quite illogical that they are not. I have the impression that the C++ language designers didn't understand that implicit conversions represent inclusion subtyping amongst value types. Yes, of course a square is a kind of polygon so there should be an implicit conversion. Indeed all specialisations of polygons should be able to "inherit" general purpose functions to calculate area, perimeter, centre of mass, moments, intersection, union, difference, triangulations, etc. There are many interesting algorithms on polygons. It would be wonderful if these become available on all the specialisations, and yet support user-defined "overrides" for optimisation. > > The result is that in C++ free functions are much better > > behaved for value-types. > > Free functions i use all the time. If something is possible to > implement as free, non-friend and non-member function effectively then > it is advisable to do so. It automatically lacks dependency on > implementation details and so it usually works after refactoring > without changes needed. Like you did show in your original post. Well said. -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Öö Tiib on 21 Apr 2010 19:59 On Apr 19, 3:04 pm, David Barrett-Lennard <davi...(a)iinet.net.au> wrote: > Consider that we implement simple datatypes (i.e. simple "value > types") without virtual functions and all read only functions are > global functions (instead of class member functions). Let single > argument constructors be used to support implicit type conversions. > E.g. to emulate Square value is-a Rect value. > > The following uses structs (public member variables) without > suggesting that is necessarily appropriate. > > struct Square { int s; }; > int side(Square sq) { return sq.s; } You pass Square by value, and that is bad idea when Square represents something with enough properties (that even squares have like location, color, inclination degree ... etc.) to pass it by reference. Better is: int side(Square const& sq) { return sq.s; } > > struct Rect > { > Rect(Square s) : w(side(s)), h(side(s)) {} This causes often secret overhead, you may get conversion copy where you did not expect it. I usually put explicit in front of it. Again ... it goes by value. > int w, h; > }; > int width(Rect r) { return r.w; } > int height(Rect r) { return r.h; } > int area(Rect r) { return width(r) * height(r); } Again pass by value. > > void Test() > { > Square s = {10}; > int A = area(s); > } If you did not make Rect constructor explicit then it works. Otherwise you have to write: int A = area(Rect(s)); That indicates to possible reader that conversion takes place. So if they want to optimize (and graphics people often do) then they write: inline int width(Square const& sq) { return side(sq); } inline int height(Square const& sq) { return side(sq); } inline int area(Square const& sq) { return width(sq)*height(sq); } As results most compilers generate equivalent of: int A = 100; Or, if A is not further used, (like in your case) with nothing. > The width, height and area functions defined on rectangle values are > also available for square values, which is just what we need, and > could be viewed as a form of inheritance. By declaring these > functions inline a modern compiler will typically eliminate > temporaries and be as efficient as a specialisation for Square. > > Square inherits functions defined on rectangles but not the > implementation of rectangle (i.e. the member variables). In fact > Square can be implemented in any way we like. E.g. by recording the > perimeter. > > struct Square { int perim; }; > int side(Square sq) { return sq.perim/4; } That works fine, with same remarks about pass by value. > Note that the implicit conversions are still available if the read > only functions use const references. E.g. area could be declared like > this > > int area(const Rect& r) > { > return width(r) * height(r); > } > > Questions: > > 1) Is this a reasonable technique? I avoid it. In large project such implicit conversions cause sometimes hard to discover bugs because they hide simple typos. > 2) Is there a reason why C++ was designed so that const member > functions defeat the implicit conversions on *this? I don't think that they do. I think that your own const-correctness and pass-by-value defects defeat it. Try if it works: struct Square { int s; int foo() const; }; int side(Square const& sq) { return sq.s; } struct Rect { Rect(Square const& s) : w(side(s)), h(side(s)) {} int w, h; }; int width(Rect const& r) { return r.w; } int height(Rect const& r) { return r.h; } int area(Rect const& r) { return width(r) * height(r); } int Square::foo() const { return area(*this); } void Test() { Square s = {10}; int A = area(s); int F = s.foo(); } -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Seungbeom Kim on 21 Apr 2010 22:08 On 2010-04-21 05:13, David Barrett-Lennard wrote: > On Apr 21, 2:46 pm, "Martin B."<0xCDCDC...(a)gmx.at> wrote: >> On 19.04.2010 14:04, David Barrett-Lennard wrote: >> >>> The width, height and area functions defined on rectangle values are >>> also available for square values, which is just what we need, and >>> could be viewed as a form of inheritance. (...) >> >> It could. With the right audience. I fear the average listener might >> be slightly confused though. > > Yes, it seems that C++ programmers reserve the term "inheritance" for > when subclassing is involved. Inheritance is a relationship between implementation, and not to be confused with subtyping, which is a relationship between types. (And to add confusion, subclasses and superclasses (not subtypes) describe the inheritance relationship.) However, in C++ (public) inheritance is the only way to achieve subtyping, so the two terms often get used interchangeably. In the OP's example, the two types don't share any implementation, so they're not in an inheritance relationship, but it seems that they are meant to be in a subtyping relationship (through implicit conversion). -- Seungbeom Kim [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
First
|
Prev
|
Next
|
Last
Pages: 1 2 3 4 Prev: What's the lifetime of temporaries in lambdas? Next: implicit conversion: ctor or cast? |