From: David Barrett-Lennard on
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; }

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

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?

2) Is there a reason why C++ was designed so that const member
functions defeat the implicit conversions on *this?

--
[ 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
On Apr 20, 10:35 pm, McNepp <mcnep...(a)googlemail.com> wrote:

> I do understand that inheritance is not always the appropriate means
> of expressing an 'is-a' relationship.
> You show a fine example: it doesn't make sense for a Square to inherit
> two values from a Rectangle if it only needs one value for a complete
> description of its nature.
>
> However, I'd still look a the indisputable 'is-a' relationship and try
> to model it in a way that makes the more specialized entity depend on
> the less specialized.
>
> In your case, I'd suggest: remove the constructor that takes a Square
> from Rectangle and add an 'operator Rectangle() const' to Square.

Why exactly?

Although it seems strange that a class is in effect declaring itself
to be a supertype of another type, I can see at least one advantage.
For example

class Rational
{
public:
Rational(int x) : num(x), denom(1) {}
...
private:
int num;
int denom;
};

This conceptually declares a supertype of a built-in type 'int'.

I think C++ programmers normally associate a subtype relationship with
subclassing and hence L-value-substitutability and not with (R-)value
substitutability which is a different thing all together. This is
despite the fact that C++ programmers commonly talk about data types
or value-types. Anyway the result seems to be they don't consider
short to be a subtype of int, or int to be a subtype of Rational.

I'm assuming an L-value is an expression that refers to a memory
location which may either be

1) a variable that holds an abstract value; or

2) a class instance (i.e. object) which doesn't represent an abstract
value. In that case all we can say is that the object represents some
finite state machine.

I suggest L-value-substitutability only has to do with
substitutability of one type of state machine for another, since it
makes little sense to talk about substitutability of one type of
variable for another. E.g. a circle variable can't be treated as an
ellipse variable by a client because the client could illegally write
an ellipse value. An ellipse variable cannot be treated as a circle
variable because a client would erroneously assume reads of the
variable only give circle values.


> {http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6-mod }

I read the ever popular circle-ellipse discussion in that link. I
suggest it misses the point. It claims it can make sense for Circle
to subclass Ellipse as long as Ellipse doesn't have a setSize() method
that allows the width and height to be changed independently.

Really, Circle and Ellipse are simple value-types and I cannot imagine
not supporting appropriate assignment operators on variables of these
types. A Circle variable cannot inherit the assignment operator that
is applicable to an Ellipse variable, so subclassing cannot model the
relationship correctly.

Subclassing is unsuitable for subtype relationships between value
types in general. Indeed ISTM user-defined coercions are really about
addressing the fact that subclassing cannot model value
substitutability correctly. Then again it looks to me that user-
defined coercions are somewhat broken in C++ as well, which is a
shame.


--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: McNepp on
I do understand that inheritance is not always the appropriate means
of expressing an 'is-a' relationship.
You show a fine example: it doesn't make sense for a Square to inherit
two values from a Rectangle if it only needs one value for a complete
description of its nature.

However, I'd still look a the indisputable 'is-a' relationship and try
to model it in a way that makes the more specialized entity depend on
the less specialized.

In your case, I'd suggest: remove the constructor that takes a Square
from Rectangle and add an 'operator Rectangle() const' to Square.

{ http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6 -mod }

--
[ 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
On Apr 22, 3:35 am, Keith H Duggar <dug...(a)alum.mit.edu> wrote:
> On Apr 19, 8:04 am, David Barrett-Lennard <davi...(a)iinet.net.au>
> 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. By declaring these
> > functions inline a modern compiler will typically eliminate
> > temporaries and be as efficient as a specialisation for Square.
>
> It's possible is /could/ be viewed as a "form" of inheritance,
> but that would of course depend on how you define inheritance.
> It certainly is not inheritance in the C++ definition.

Agreed

> And no
> matter what definition you choose, if it allowed this to be a
> "form of inheritance" I would argue that said definition is at
> total odds with any common sense meaning of "inheritance".

I was thinking of it as "inheritance" in the following sense: Let a
data type mean a set of abstract values plus operators on those values
(using operator in an algebraic sense). The "behaviour" of a datatype
is only externally visible through those operators. Relating this
back to C++ code, assuming all operators are expressed as free
functions without in-out parameters, an implicit conversion between
two datatypes means that all the operators of one are available to the
other - because of value substitutability. I think it is reasonable
to call that "inheritance" treating the word as simply meaning that
"stuff" associated with one "thing" is automatically available to
another "thing". Anyway that was the only point I wanted to make! I
just looked for synonyms of "inherit" at synonym.com and the only
alternatives it gave me are "get" and "acquire".

> Suppose I create a coercion from complex<int,int> to rectangle,
> does complex<int,int> now "inherit" from rectangle? What if we
> add a coercion from rectangle to complex<int,int>, does rectangle
> no circularly inherit from complex?

That's just weird so I'm not sure what your point is here. A better
example for me would be classes named Polar and Cartesian and we would
like implicit conversions in both directions. I haven't studied the
rules of implicit conversions for C++ to know whether such a thing
works in practise, but in principle I can't see any problems with
alternative representations of abstract values.

BTW the conversions between polar and cartesian representations
involve cos, sin, sqrt, atan2 which are inexact for rational numbers
(including floats). That could mean we have an example of the
following assertion breaking which I find extremely unappealing:

T1 x;
T2 y = x;
assert(x == y);

This can fail if coercions aren't invertible and y is coerced in order
to perform comparison using T1::operator==. On that basis I would
reject implicit conversions between polar and cartesian
representations.

I would hope that implicit conversions between datatypes satisfy
reasonable "laws" to make program correctness easy to reason about.

Unfortunately C++ doesn't give you a transitive closure of the
defined implicit conversions. Consider that we extend my previous
example with an additional type to represent an arbitrary
quadrilateral and we recognise that square value is-a rectangle value
is-a quadrilateral value. In addition, assume that we haven't defined
an area function specialised for squares or rectangles. Unfortunately
the following won't compile:

struct Quad
{
Quad(Rect r)
{
x[0] = width(r);
y[0] = 0;
x[1] = width(r);
y[1] = height(r);
x[2] = 0;
y[2] = height(r);
}
int x[3];
int y[3];
};

int area(Quad q)
{
return ( - q.x[0]*q.y[0] +
(q.x[0]-q.x[1])*(q.y[0]+q.y[1]) +
(q.x[1]-q.x[2])*(q.y[1]+q.y[2]) +
q.x[2]*q.y[2]
) / 2;
}

void Test()
{
Square s = {10};
int A = area(s); // error: no implicit conversion
}

In any case even a modern C++ compiler wouldn't optimise the code
adequately (and that's why "overrides" would be very useful even
though logically they are redundant).

I would expect that whenever there are cycles in the value
substitutability graph then in principle we would expect implicit
conversions to be available from any one to any other type in the
cycle. But that's going beyond C++ capabilities.


> What about all the numerous
> other types we made add in future, are we now talking about
> multiple inheritance?

In the sense of inheritance I described above, definitely!

Related to this are union types which unfortunately aren't supported
adequately in C++.

> Not anywhere close in my mind. How would
> such "inheritance" thinking or terminology even be useful?

In the case of square value isa rectangle value, it is useful in the
sense that it is a reminder that one doesn't necessarily need to
implement operators on square that have already been written for
rectangles. If one forgets that then one may end up writing more code
than needed (and the C++ compiler won't complain of course).


> > Square inherits functions defined on rectangles but not
>
> No it doesn't "inherit" the functions. Instead you have created
> an algebra in which an implicit coercion makes syntax such as
>
> width(square)
>
> valid. Suppose I now declare and explicit function for square
>
> int area ( Square s ) { return side(s) * side(s) ; }
>
> would you now propose to say that I have "overridden the
> inherited (from rectangle) area function"? That doesn't make
> any bit of sense to me. "inheritance" has no place here.

I'm not sure why. I thought the "override" metaphor was quite a good
one. I would however say it's a very dangerous analogy because it
means something quite different to overrides of virtual methods in the
context of subclassing. However, on the other hand we are talking
about data types here and we know that subclassing is completely and
universally useless for data types so I would suggest that confusion
cannot occur for C++ programmers that properly understand datatypes.

I would say that such an "override" must not be allowed to change the
externally visible behaviour, but it can be useful to optimise
performance when the compiler is inadequate.

For any data types T1,T2 and unary operator foo I don't believe the
assertion below should be allowed to fail:

T1 x;
T2 y = x;
assert(foo(x) == foo(y));

"overrides" would need to respect that (for example).

--
[ 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
On Apr 22, 6:59 pm, �� Tiib <oot...(a)hot.ee> wrote:
> On Apr 19, 3:04 pm, David Barrett-Lennard <davi...(a)iinet.net.au>
> wrote:
>
> > 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; }

Yes I implied that was a valid option in the original post. With in-
lining it's unlikely to make any difference. In this particular case
if the compiler really did pass by reference it could well be slower
because of the indirection.


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

Yes but the whole point of this exercise was to allow implicit
conversions.


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

That happens with modern compilers anyway with my original example, so
writing all that extra code is unnecessary.


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

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.


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

Pass by value is hardly a "defect" (at least in the code I presented).


>
> 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();
> }

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. The result is that in C++ free functions are much better
behaved for value-types. Indeed this is a common reason for using
friend functions.



--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]