Search This Blog

Friday, April 28, 2023

Operator overloading

https://en.wikipedia.org/wiki/Operator_overloading

In computer programming, operator overloading, sometimes termed operator ad hoc polymorphism, is a specific case of polymorphism, where different operators have different implementations depending on their arguments. Operator overloading is generally defined by a programming language, a programmer, or both.

Rationale

Operator overloading is syntactic sugar, and is used because it allows programming using notation nearer to the target domain and allows user-defined types a similar level of syntactic support as types built into a language. It is common, for example, in scientific computing, where it allows computing representations of mathematical objects to be manipulated with the same syntax as on paper.

Operator overloading does not change the expressive power of a language (with functions), as it can be emulated using function calls. For example, consider variables a, b and c of some user-defined type, such as matrices:

a + b * c

In a language that supports operator overloading, and with the usual assumption that the '*' operator has higher precedence than the '+' operator, this is a concise way of writing:

Add(a, Multiply(b, c))

However, the former syntax reflects common mathematical usage.

Examples

In this case, the addition operator is overloaded to allow addition on a user-defined type Time in C++:

Time operator+(const Time& lhs, const Time& rhs) {
  Time temp = lhs;
  temp.seconds += rhs.seconds;
  temp.minutes += temp.seconds / 60;
  temp.seconds %= 60;
  temp.minutes += rhs.minutes;
  temp.hours += temp.minutes / 60;
  temp.minutes %= 60;
  temp.hours += rhs.hours;
  return temp;
}

Addition is a binary operation, which means it has two operands. In C++, the arguments being passed are the operands, and the temp object is the returned value.

The operation could also be defined as a class method, replacing lhs by the hidden this argument; However, this forces the left operand to be of type Time:

// The "const" right before the opening curly brace means that |this| is not modified.
Time Time::operator+(const Time& rhs) const {
  Time temp = *this;  // |this| should not be modified, so make a copy.
  temp.seconds += rhs.seconds;
  temp.minutes += temp.seconds / 60;
  temp.seconds %= 60;
  temp.minutes += rhs.minutes;
  temp.hours += temp.minutes / 60;
  temp.minutes %= 60;
  temp.hours += rhs.hours;
  return temp;
}

Note that a unary operator defined as a class method would receive no apparent argument (it only works from this):

bool Time::operator!() const {
  return hours == 0 && minutes == 0 && seconds == 0;
}

The less-than (<) operator is often overloaded to sort a structure or class:

class Pair {
 public:
  bool operator<(const Pair& p) const {
    if (x_ == p.x_) {
      return y_ < p.y_;
    }
    return x_ < p.x_;
  }

 private:
  int x_;
  int y_;
};

Like with the previous examples, in the last example operator overloading is done within the class. In C++, after overloading the less-than operator (<), standard sorting functions can be used to sort some classes.

Criticisms

Operator overloading has often been criticized because it allows programmers to reassign the semantics of operators depending on the types of their operands. For example, the use of the << operator in C++ a << b shifts the bits in the variable a left by b bits if a and b are of an integer type, but if a is an output stream then the above code will attempt to write a b to the stream. Because operator overloading allows the original programmer to change the usual semantics of an operator and to catch any subsequent programmers by surprise, it is considered good practice to use operator overloading with care (the creators of Java decided not to use this feature, although not necessarily for this reason).

Another, more subtle, issue with operators is that certain rules from mathematics can be wrongly expected or unintentionally assumed. For example, the commutativity of + (i.e. that a + b == b + a) does not always apply; an example of this occurs when the operands are strings, since + is commonly overloaded to perform a concatenation of strings (i.e. "bird" + "song" yields "birdsong", while "song" + "bird" yields "songbird"). A typical counter to this argument comes directly from mathematics: While + is commutative on integers (and more generally any complex number), it is not commutative for other "types" of variables. In practice, + is not even always associative, for example with floating-point values due to rounding errors. Another example: In mathematics, multiplication is commutative for real and complex numbers but not commutative in matrix multiplication.

Timeline of operator overloading

1960s

The ALGOL 68 specification allowed operator overloading.

Extract from the ALGOL 68 language specification (page 177) where the overloaded operators ¬, =, ≠, and abs are defined:

10.2.2. Operations on Boolean Operands
a) op ∨ = (bool a, b) bool:( a | true | b );
b) op ∧ = (bool a, b) bool: ( a | b | false );
c) op ¬ = (bool a) bool: ( a | false | true );
d) op = = (bool a, b) bool:( a∧b ) ∨ ( ¬b∧¬a );
e) op ≠ = (bool a, b) bool: ¬(a=b);
f) op abs = (bool a)int: ( a | 1 | 0 );

Note that no special declaration is needed to overload an operator, and the programmer is free to create new operators. For dyadic operators their priority compared to other operators can be set:

 prio max = 9;
 
 op max = (int a, b) int: ( a>b | a | b );
 op ++ = (ref int a) int: ( a +:= 1 );

1980s

Ada supports overloading of operators from its inception, with the publication of the Ada 83 language standard. However, the language designers chose to preclude the definition of new operators. Only extant operators in the language may be overloaded, by defining new functions with identifiers such as "+", "*", "&" etc. Subsequent revisions of the language (in 1995 and 2005) maintain the restriction to overloading of extant operators.

In C++, operator overloading is more refined than in ALGOL 68.

1990s

Java language designers at Sun Microsystems chose to omit overloading.

Python allows operator overloading through the implementation of methods with special names. For example, the addition (+) operator can be overloaded by implementing the method obj.__add__(self, other).

Ruby allows operator overloading as syntactic sugar for simple method calls.

Lua allows operator overloading as syntactic sugar for method calls with the added feature that if the first operand doesn't define that operator, the method for the second operand will be used.

2000s

Microsoft added operator overloading to C# in 2001 and to Visual Basic .NET in 2003.

Scala treats all operators as methods and thus allows operator overloading by proxy.

In Raku, the definition of all operators is delegated to lexical functions, and so, using function definitions, operators can be overloaded or new operators added. For example, the function defined in the Rakudo source for incrementing a Date object with "+" is:

multi infix:<+>(Date:D $d, Int:D $x) {
    Date.new-from-daycount($d.daycount + $x)
}

Since "multi" was used, the function gets added to the list of multidispatch candidates, and "+" is only overloaded for the case where the type constraints in the function signature are met. While the capacity for overloading includes +, *, >=, the postfix and term i, and so on, it also allows for overloading various brace operators: "[x, y]", "x[ y ]", "x{ y }", and "x( y )".

Kotlin has supported operator overloading since its creation.

Polymorphism (computer science)

https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. The concept is borrowed from a principle in biology where an organism or species can have many different forms or stages.

The most commonly recognized major classes of polymorphism are:

  • Ad hoc polymorphism: defines a common interface for an arbitrary set of individually specified types.
  • Parametric polymorphism: not specifying concrete types and instead use abstract symbols that can substitute for any type.
  • Subtyping (also called subtype polymorphism or inclusion polymorphism): when a name denotes instances of many different classes related by some common superclass.


History

Interest in polymorphic type systems developed significantly in the 1990s, with practical implementations beginning to appear by the end of the decade. Ad hoc polymorphism and parametric polymorphism were originally described in Christopher Strachey's Fundamental Concepts in Programming Languages, where they are listed as "the two main classes" of polymorphism. Ad hoc polymorphism was a feature of Algol 68, while parametric polymorphism was the core feature of ML's type system.

In a 1985 paper, Peter Wegner and Luca Cardelli introduced the term inclusion polymorphism to model subtypes and inheritance, citing Simula as the first programming language to implement it.

Types

Ad hoc polymorphism

Christopher Strachey chose the term ad hoc polymorphism to refer to polymorphic functions that can be applied to arguments of different types, but that behave differently depending on the type of the argument to which they are applied (also known as function overloading or operator overloading). The term "ad hoc" in this context is not intended to be pejorative; it refers simply to the fact that this type of polymorphism is not a fundamental feature of the type system. In the Pascal / Delphi example below, the Add functions seem to work generically over various types when looking at the invocations, but are considered to be two entirely distinct functions by the compiler for all intents and purposes:

program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                   (* Prints "3"             *)
    Writeln(Add('Hello, ', 'Mammals!'));    (* Prints "Hello, Mammals!" *)
end.

In dynamically typed languages the situation can be more complex as the correct function that needs to be invoked might only be determinable at run time.

Implicit type conversion has also been defined as a form of polymorphism, referred to as "coercion polymorphism".

Parametric polymorphism

Parametric polymorphism allows a function or a data type to be written generically, so that it can handle values uniformly without depending on their type. Parametric polymorphism is a way to make a language more expressive while still maintaining full static type-safety.

The concept of parametric polymorphism applies to both data types and functions. A function that can evaluate to or be applied to values of different types is known as a polymorphic function. A data type that can appear to be of a generalized type (e.g. a list with elements of arbitrary type) is designated polymorphic data type like the generalized type from which such specializations are made.

Parametric polymorphism is ubiquitous in functional programming, where it is often simply referred to as "polymorphism". The following example in Haskell shows a parameterized list data type and two parametrically polymorphic functions on them:

data List a = Nil | Cons a (List a)

length :: List a -> Integer
length Nil         = 0
length (Cons x xs) = 1 + length xs

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = Cons (f x) (map f xs)

Parametric polymorphism is also available in several object-oriented languages. For instance, templates in C++ and D, or under the name generics in C#, Delphi, Java and Go:

class List<T> {
    class Node<T> {
        T elem;
        Node<T> next;
    }
    Node<T> head;
    int length() { ... }
}

List<B> map(Func<A, B> f, List<A> xs) {
    ...
}

John C. Reynolds (and later Jean-Yves Girard) formally developed this notion of polymorphism as an extension to lambda calculus (called the polymorphic lambda calculus or System F). Any parametrically polymorphic function is necessarily restricted in what it can do, working on the shape of the data instead of its value, leading to the concept of parametricity.

Subtyping

Some languages employ the idea of subtyping (also called subtype polymorphism or inclusion polymorphism) to restrict the range of types that can be used in a particular case of polymorphism. In these languages, subtyping allows a function to be written to take an object of a certain type T, but also work correctly, if passed an object that belongs to a type S that is a subtype of T (according to the Liskov substitution principle). This type relation is sometimes written S <: T. Conversely, T is said to be a supertype of S—written T :> S. Subtype polymorphism is usually resolved dynamically (see below).

In the following Java example we make cats and dogs subtypes of animals. The procedure letsHear() accepts an animal, but will also work correctly if a subtype is passed to it:

abstract class Animal {
    abstract String talk();
}

class Cat extends Animal {
    String talk() {
        return "Meow!";
    }
}

class Dog extends Animal {
    String talk() {
        return "Woof!";
    }
}

static void letsHear(final Animal a) {
    println(a.talk());
}

static void main(String[] args) {
    letsHear(new Cat());
    letsHear(new Dog());
}

In another example, if Number, Rational, and Integer are types such that Number :> Rational and Number :> Integer, a function written to take a Number will work equally well when passed an Integer or Rational as when passed a Number. The actual type of the object can be hidden from clients into a black box, and accessed via object identity. In fact, if the Number type is abstract, it may not even be possible to get your hands on an object whose most-derived type is Number (see abstract data type, abstract class). This particular kind of type hierarchy is known—especially in the context of the Scheme programming language—as a numerical tower, and usually contains many more types.

Object-oriented programming languages offer subtype polymorphism using subclassing (also known as inheritance). In typical implementations, each class contains what is called a virtual table—a table of functions that implement the polymorphic part of the class interface—and each object contains a pointer to the "vtable" of its class, which is then consulted whenever a polymorphic method is called. This mechanism is an example of:

  • late binding, because virtual function calls are not bound until the time of invocation;
  • single dispatch (i.e. single-argument polymorphism), because virtual function calls are bound simply by looking through the vtable provided by the first argument (the this object), so the runtime types of the other arguments are completely irrelevant.

The same goes for most other popular object systems. Some, however, such as Common Lisp Object System, provide multiple dispatch, under which method calls are polymorphic in all arguments.

The interaction between parametric polymorphism and subtyping leads to the concepts of variance and bounded quantification.

Row polymorphism

Row polymorphism is a similar, but distinct concept from subtyping. It deals with structural types. It allows the usage of all values whose types have certain properties, without losing the remaining type information.

Polytypism

A related concept is polytypism (or data type genericity). A polytypic function is more general than polymorphic, and in such a function, "though one can provide fixed ad hoc cases for specific data types, an ad hoc combinator is absent".

Rank polymorphism

Rank polymorphism is one of the defining features of the array programming languages, like APL. The essence of the rank-polymorphic programming model is implicitly treating all operations as aggregate operations, usable on arrays with arbitrarily many dimensions, which is to say that rank polymorphism allows functions to be defined to operate on arrays of any shape and size.

Implementation aspects

Static and dynamic polymorphism

Polymorphism can be distinguished by when the implementation is selected: statically (at compile time) or dynamically (at run time, typically via a virtual function). This is known respectively as static dispatch and dynamic dispatch, and the corresponding forms of polymorphism are accordingly called static polymorphism and dynamic polymorphism.

Static polymorphism executes faster, because there is no dynamic dispatch overhead, but requires additional compiler support. Further, static polymorphism allows greater static analysis by compilers (notably for optimization), source code analysis tools, and human readers (programmers). Dynamic polymorphism is more flexible but slower—for example, dynamic polymorphism allows duck typing, and a dynamically linked library may operate on objects without knowing their full type.

Static polymorphism typically occurs in ad hoc polymorphism and parametric polymorphism, whereas dynamic polymorphism is usual for subtype polymorphism. However, it is possible to achieve static polymorphism with subtyping through more sophisticated use of template metaprogramming, namely the curiously recurring template pattern.

When polymorphism is exposed via a library, static polymorphism becomes impossible for dynamic libraries as there is no way of knowing what types the parameters are when the shared object is built. While languages like C++ and Rust use monomorphized templates, the Swift programming language makes extensive use of dynamic dispatch to build the application binary interface for these libraries by default. As a result, more code can be shared for a reduced system size at the cost of runtime overhead.

Inheritance (object-oriented programming)

In object-oriented programming, inheritance is the mechanism of basing an object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Also defined as deriving new classes (sub classes) from existing ones such as super class or base class and then forming them into a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance, a "child object", acquires all the properties and behaviors of the "parent object" , with the exception of: constructors, destructors, overloaded operators and friend functions of the base class. Inheritance allows programmers to create classes that are built upon existing classes, to specify a new implementation while maintaining the same behaviors (realizing an interface), to reuse code and to independently extend original software via public classes and interfaces. The relationships of objects or classes through inheritance give rise to a directed acyclic graph.

An inherited class is called a subclass of its parent class or super class. The term "inheritance" is loosely used for both class-based and prototype-based programming, but in narrow use the term is reserved for class-based programming (one class inherits from another), with the corresponding technique in prototype-based programming being instead called delegation (one object delegates to another). Class-modifying inheritance patterns can be pre-defined according to simple network interface parameters such that inter-language compatibility is preserved.

Inheritance should not be confused with subtyping. In some languages inheritance and subtyping agree, whereas in others they differ; in general, subtyping establishes an is-a relationship, whereas inheritance only reuses implementation and establishes a syntactic relationship, not necessarily a semantic relationship (inheritance does not ensure behavioral subtyping). To distinguish these concepts, subtyping is sometimes referred to as interface inheritance (without acknowledging that the specialization of type variables also induces a subtyping relation), whereas inheritance as defined here is known as implementation inheritance or code inheritance. Still, inheritance is a commonly used mechanism for establishing subtype relationships.

Inheritance is contrasted with object composition, where one object contains another object (or objects of one class contain objects of another class); see composition over inheritance. Composition implements a has-a relationship, in contrast to the is-a relationship of subtyping.

History

In 1966, Tony Hoare presented some remarks on records, and in particular presented the idea of record subclasses, record types with common properties but discriminated by a variant tag and having fields private to the variant. Influenced by this, in 1967 Ole-Johan Dahl and Kristen Nygaard presented a design that allowed specifying objects that belonged to different classes but had common properties. The common properties were collected in a superclass, and each superclass could itself potentially have a superclass. The values of a subclass were thus compound objects, consisting of some number of prefix parts belonging to various superclasses, plus a main part belonging to the subclass. These parts were all concatenated together. The attributes of a compound object would be accessible by dot notation. This idea was first adopted in the Simula 67 programming language. The idea then spread to Smalltalk, C++, Java, Python, and many other languages.

Types

Single inheritance
 
Multiple inheritance

There are various types of inheritance, based on paradigm and specific language.

Single inheritance
where subclasses inherit the features of one superclass. A class acquires the properties of another class.
Multiple inheritance
where one class can have more than one superclass and inherit features from all parent classes.

"Multiple inheritance ... was widely supposed to be very difficult to implement efficiently. For example, in a summary of C++ in his book on Objective C, Brad Cox actually claimed that adding multiple inheritance to C++ was impossible. Thus, multiple inheritance seemed more of a challenge. Since I had considered multiple inheritance as early as 1982 and found a simple and efficient implementation technique in 1984, I couldn't resist the challenge. I suspect this to be the only case in which fashion affected the sequence of events."

Multilevel inheritance
where a subclass is inherited from another subclass. It is not uncommon that a class is derived from another derived class as shown in the figure "Multilevel inheritance".
 
Multilevel inheritance
 
The class A serves as a base class for the derived class B, which in turn serves as a base class for the derived class C. The class B is known as intermediate base class because it provides a link for the inheritance between A and C. The chain ABC is known as inheritance path.
A derived class with multilevel inheritance is declared as follows:
class A { ... };      // Base class
class B : public A { ... };   // B derived from A
class C : public B { ... };   // C derived from B
This process can be extended to any number of levels.
Hierarchical inheritance
This is where one class serves as a superclass (base class) for more than one sub class. For example, a parent class, A, can have two subclasses B and C. Both B and C's parent class is A, but B and C are two separate subclasses.
Hybrid inheritance
Hybrid inheritance is when a mix of two or more of the above types of inheritance occurs. An example of this is when class A has a subclass B which has two subclasses, C and D. This is a mixture of both multilevel inheritance and hierarchal inheritance.

Subclasses and superclasses

Subclasses, derived classes, heir classes, or child classes are modular derivative classes that inherits one or more language entities from one or more other classes (called superclass, base classes, or parent classes). The semantics of class inheritance vary from language to language, but commonly the subclass automatically inherits the instance variables and member functions of its superclasses.

The general form of defining a derived class is:

class SubClass: visibility SuperClass
{
    // subclass members
};
  • The colon indicates that the subclass inherits from the superclass. The visibility is optional and, if present, may be either private or public. The default visibility is private. Visibility specifies whether the features of the base class are privately derived or publicly derived.

Some languages support also the inheritance of other constructs. For example, in Eiffel, contracts that define the specification of a class are also inherited by heirs. The superclass establishes a common interface and foundational functionality, which specialized subclasses can inherit, modify, and supplement. The software inherited by a subclass is considered reused in the subclass. A reference to an instance of a class may actually be referring to one of its subclasses. The actual class of the object being referenced is impossible to predict at compile-time. A uniform interface is used to invoke the member functions of objects of a number of different classes. Subclasses may replace superclass functions with entirely new functions that must share the same method signature.

Non-subclassable classes

In some languages a class may be declared as non-subclassable by adding certain class modifiers to the class declaration. Examples include the final keyword in Java and C++11 onwards or the sealed keyword in C#. Such modifiers are added to the class declaration before the class keyword and the class identifier declaration. Such non-subclassable classes restrict reusability, particularly when developers only have access to precompiled binaries and not source code.

A non-subclassable class has no subclasses, so it can be easily deduced at compile time that references or pointers to objects of that class are actually referencing instances of that class and not instances of subclasses (they don't exist) or instances of superclasses (upcasting a reference type violates the type system). Because the exact type of the object being referenced is known before execution, early binding (also called static dispatch) can be used instead of late binding (also called dynamic dispatch), which requires one or more virtual method table lookups depending on whether multiple inheritance or only single inheritance are supported in the programming language that is being used.

Non-overridable methods

Just as classes may be non-subclassable, method declarations may contain method modifiers that prevent the method from being overridden (i.e. replaced with a new function with the same name and type signature in a subclass). A private method is un-overridable simply because it is not accessible by classes other than the class it is a member function of (this is not true for C++, though). A final method in Java, a sealed method in C# or a frozen feature in Eiffel cannot be overridden.

Virtual methods

If the superclass method is a virtual method, then invocations of the superclass method will be dynamically dispatched. Some languages require that methods be specifically declared as virtual (e.g. C++), and in others, all methods are virtual (e.g. Java). An invocation of a non-virtual method will always be statically dispatched (i.e. the address of the function call is determined at compile-time). Static dispatch is faster than dynamic dispatch and allows optimizations such as inline expansion.

Visibility of inherited members

The following table shows which variables and functions get inherited dependent on the visibility given when deriving the class, using the terminology established by C++.

Base class visibility Derived class visibility

Private derivation Protected derivation Public derivation
  • Private →
  • Protected →
  • Public →
  • Not inherited
  • Private
  • Private
  • Not inherited
  • Protected
  • Protected
  • Not inherited
  • Protected
  • Public

Applications

Inheritance is used to co-relate two or more classes to each other.

Overriding

Illustration of method overriding

Many object-oriented programming languages permit a class or object to replace the implementation of an aspect—typically a behavior—that it has inherited. This process is called overriding. Overriding introduces a complication: which version of the behavior does an instance of the inherited class use—the one that is part of its own class, or the one from the parent (base) class? The answer varies between programming languages, and some languages provide the ability to indicate that a particular behavior is not to be overridden and should behave as defined by the base class. For instance, in C#, the base method or property can only be overridden in a subclass if it is marked with the virtual, abstract, or override modifier, while in programming languages such as Java, different methods can be called to override other methods. An alternative to overriding is hiding the inherited code.

Code reuse

Implementation inheritance is the mechanism whereby a subclass re-uses code in a base class. By default the subclass retains all of the operations of the base class, but the subclass may override some or all operations, replacing the base-class implementation with its own.

In the following Python example, subclasses SquareSumComputer and CubeSumComputer override the transform() method of the base class SumComputer. The base class comprises operations to compute the sum of the squares between two integers. The subclass re-uses all of the functionality of the base class with the exception of the operation that transforms a number into its square, replacing it with an operation that transforms a number into its square and cube respectively. The subclasses therefore compute the sum of the squares/cubes between two integers.

Below is an example of Python.

class SumComputer:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def transform(self, x):
        raise NotImplementedError

    def inputs(self):
        return range(self.a, self.b)

    def compute(self):
        return sum(self.transform(value) for value in self.inputs())

class SquareSumComputer(SumComputer):
    def transform(self, x):
        return x * x

class CubeSumComputer(SumComputer):
    def transform(self, x):
        return x * x * x

In most quarters, class inheritance for the sole purpose of code reuse has fallen out of favor.[citation needed] The primary concern is that implementation inheritance does not provide any assurance of polymorphic substitutability—an instance of the reusing class cannot necessarily be substituted for an instance of the inherited class. An alternative technique, explicit delegation, requires more programming effort, but avoids the substitutability issue. In C++ private inheritance can be used as a form of implementation inheritance without substitutability. Whereas public inheritance represents an "is-a" relationship and delegation represents a "has-a" relationship, private (and protected) inheritance can be thought of as an "is implemented in terms of" relationship.

Another frequent use of inheritance is to guarantee that classes maintain a certain common interface; that is, they implement the same methods. The parent class can be a combination of implemented operations and operations that are to be implemented in the child classes. Often, there is no interface change between the supertype and subtype- the child implements the behavior described instead of its parent class.

Inheritance vs subtyping

Inheritance is similar to but distinct from subtyping. Subtyping enables a given type to be substituted for another type or abstraction, and is said to establish an is-a relationship between the subtype and some existing abstraction, either implicitly or explicitly, depending on language support. The relationship can be expressed explicitly via inheritance in languages that support inheritance as a subtyping mechanism. For example, the following C++ code establishes an explicit inheritance relationship between classes B and A, where B is both a subclass and a subtype of A, and can be used as an A wherever a B is specified (via a reference, a pointer or the object itself).

class A {
 public:
  void DoSomethingALike() const {}
};

class B : public A {
 public:
  void DoSomethingBLike() const {}
};

void UseAnA(const A& a) {
  a.DoSomethingALike();
}

void SomeFunc() {
  B b;
  UseAnA(b);  // b can be substituted for an A.
}

In programming languages that do not support inheritance as a subtyping mechanism, the relationship between a base class and a derived class is only a relationship between implementations (a mechanism for code reuse), as compared to a relationship between types. Inheritance, even in programming languages that support inheritance as a subtyping mechanism, does not necessarily entail behavioral subtyping. It is entirely possible to derive a class whose object will behave incorrectly when used in a context where the parent class is expected; see the Liskov substitution principle.  (Compare connotation/denotation.) In some OOP languages, the notions of code reuse and subtyping coincide because the only way to declare a subtype is to define a new class that inherits the implementation of another.

Design constraints

Using inheritance extensively in designing a program imposes certain constraints.

For example, consider a class Person that contains a person's name, date of birth, address and phone number. We can define a subclass of Person called Student that contains the person's grade point average and classes taken, and another subclass of Person called Employee that contains the person's job-title, employer, and salary.

In defining this inheritance hierarchy we have already defined certain restrictions, not all of which are desirable:

Singleness
Using single inheritance, a subclass can inherit from only one superclass. Continuing the example given above, a Person object can be either a Student or an Employee, but not both. Using multiple inheritance partially solves this problem, as one can then define a StudentEmployee class that inherits from both Student and Employee. However, in most implementations, it can still inherit from each superclass only once, and thus, does not support cases in which a student has two jobs or attends two institutions. The inheritance model available in Eiffel makes this possible through support for repeated inheritance.
Static
The inheritance hierarchy of an object is fixed at instantiation when the object's type is selected and does not change with time. For example, the inheritance graph does not allow a Student object to become an Employee object while retaining the state of its Person superclass. (This kind of behavior, however, can be achieved with the decorator pattern.) Some have criticized inheritance, contending that it locks developers into their original design standards.
Visibility
Whenever client code has access to an object, it generally has access to all the object's superclass data. Even if the superclass has not been declared public, the client can still cast the object to its superclass type. For example, there is no way to give a function a pointer to a Student's grade point average and transcript without also giving that function access to all of the personal data stored in the student's Person superclass. Many modern languages, including C++ and Java, provide a "protected" access modifier that allows subclasses to access the data, without allowing any code outside the chain of inheritance to access it.

The composite reuse principle is an alternative to inheritance. This technique supports polymorphism and code reuse by separating behaviors from the primary class hierarchy and including specific behavior classes as required in any business domain class. This approach avoids the static nature of a class hierarchy by allowing behavior modifications at run time and allows one class to implement behaviors buffet-style, instead of being restricted to the behaviors of its ancestor classes.

Issues and alternatives

Implementation inheritance is controversial among programmers and theoreticians of object-oriented programming since at least the 1990s. Among them are the authors of Design Patterns, who advocate interface inheritance instead, and favor composition over inheritance. For example, the decorator pattern (as mentioned above) has been proposed to overcome the static nature of inheritance between classes. As a more fundamental solution to the same problem, role-oriented programming introduces a distinct relationship, played-by, combining properties of inheritance and composition into a new concept.

According to Allen Holub, the main problem with implementation inheritance is that it introduces unnecessary coupling in the form of the "fragile base class problem": modifications to the base class implementation can cause inadvertent behavioral changes in subclasses. Using interfaces avoids this problem because no implementation is shared, only the API. Another way of stating this is that "inheritance breaks encapsulation". The problem surfaces clearly in open object-oriented systems such as frameworks, where client code is expected to inherit from system-supplied classes and then substituted for the system's classes in its algorithms.

Reportedly, Java inventor James Gosling has spoken against implementation inheritance, stating that he would not include it if he were to redesign Java. Language designs that decouple inheritance from subtyping (interface inheritance) appeared as early as 1990; a modern example of this is the Go programming language.

Complex inheritance, or inheritance used within an insufficiently mature design, may lead to the yo-yo problem. When inheritance was used as a primary approach to structure programs in the late 1990s, developers tended to break code into more layers of inheritance as the system functionality grew. If a development team combined multiple layers of inheritance with the single responsibility principle, this resulted in many very thin layers of code, with many layers consisting of only 1 or 2 lines of actual code. Too many layers make debugging a significant challenge, as it becomes hard to determine which layer needs to be debugged.

Another issue with inheritance is that subclasses must be defined in code, which means that program users cannot add new subclasses at runtime. Other design patterns (such as Entity–component–system) allow program users to define variations of an entity at runtime.

C++20

C++20 is a version of the ISO/IEC 14882 standard for the C++ programming language. C++20 replaced the prior version of the C++ standard, called C++17. The standard was technically finalized by WG21 at the meeting in Prague in February 2020,[3] approved on 4 September 2020, and published in December 2020.

Features

C++20 adds more new major features than C++14 or C++17. Changes that have been accepted into C++20 include:

Language

  • concepts, with terse syntax
  • modules
  • designated initializers (based on the C99 feature, and common g++ extension)
  • [=, this] as a lambda capture
  • template parameter lists on lambdas
  • three-way comparison using the "spaceship operator", operator <=>
  • initialization of an additional variable within a range-based for statement
  • lambdas in unevaluated contexts
  • default constructible and assignable stateless lambdas
  • allow pack expansions in lambda init-capture
  • class types in non-type template parameters, also allowing string literals as template parameters
  • removing the need for typename in certain circumstances
  • new standard attributes [[no_unique_address]], [[likely]] and [[unlikely]]
  • conditional explicit, allowing the explicit modifier to be contingent on a boolean expression
  • expanded constexpr: virtual functions, union, try and catch, dynamic_cast and typeid, std::pointer_traits
  • immediate functions using the new consteval keyword
  • signed integers are now defined to be represented using two's complement (signed integer overflow remains undefined behavior)
  • a revised memory model
  • various improvements to structured bindings (interaction with lambda captures, static and thread_local storage duration)
  • coroutines
  • using on scoped enums
  • constinit keyword

Library

  • ranges (The One Ranges Proposal)
  • std::make_shared and std::allocate_shared for arrays
  • atomic smart pointers (such as std::atomic<shared_ptr<T>> and std::atomic<weak_ptr<T>>)
  • std::to_address to convert a pointer to a raw pointer
  • calendar and time-zone additions to <chrono>
  • std::span, providing a view to a contiguous array (analogous to std::string_view but span can mutate the referenced sequence)
  • std::erase and std::erase_if, simplifying element erasure for most standard containers
  • <version> header
  • std::bit_cast<> for type casting of object representations, with less verbosity than memcpy() and more ability to exploit compiler internals
  • feature test macros
  • various constexpr library bits
  • smart pointer creation with default initialization
  • contains-method for associative containers
  • bit operations, such as leading/trailing zero/one count, and log2 operations
  • std::bind_front

New and changed keywords

Many new keywords added (and the new "spaceship operator", operator <=>), such as concept, constinit, consteval, co_await, co_return, co_yield, requires (plus changed meaning for export), and char8_t (for UTF-8 support). And explicit can take an expression since C++20. Most of the uses of the volatile keyword have been deprecated.

In addition to keywords, there are identifiers with special meaning, including new import and module.

New attributes in C++20: [[likely]], [[unlikely]], and [[no_unique_address]]

Removed and deprecated

Removed features:

  • The C-derived headers <ccomplex>, <ciso646>, <cstdalign>, <cstdbool> and <ctgmath> were removed, as they serve no purpose in C++. (The corresponding <*.h> headers remain, for compatibility with C.)
  • The use of throw() as an exception specification was removed.
  • Some previously deprecated library features were removed, including std::uncaught_exception, std::raw_storage_iterator, std::is_literal_type, std::is_literal_type_v, std::result_of and std::result_of_t.

Deprecated features:

  • Use of comma operator in subscript expressions has been deprecated
  • (most of) volatile has been deprecated

Published as Technical Specifications

  • Parallelism TS v2 (including task blocks)
  • Reflection TS v1
  • Networking TS v1

Deferred to a later standard

  • Contracts – a new study group (SG21) has been formed to work on a new proposal
  • Reflection
  • Metaclasses
  • Executors
  • Networking extensions, including async, basic I/O services, timers, buffers and buffer-oriented streams, sockets, and Internet protocols (blocked by executors)
  • Properties
  • Extended futures

Compiler support

Full support

  • Visual Studio 2019 supports all C++20 features through its /std:c++latest option, as of version 16.10.0. An option /std:c++20 to enable C++20 mode is added in version 16.11.0. Project properties→Configuration properties→C/C++→Language→C++ Language Standard.

Microsoft's compiler doesn't just support Windows, also Linux (and e.g. Android and iOS), while it then requires the "Visual C++ for Linux Development extension".

Partial

  • Clang has partial C++20 support that can be enabled with the option -std=c++20 (version 10 and later) or -std=c++2a (version 9 and earlier).
  • EDG eccp started implementing C++20 features in version 5.0 and as of version 6.1 supports most C++20 core language features.
  • GCC added partial, experimental C++20 support in 2017 in version 8 through the option -std=c++2a. Like Clang, GCC replaced this option with -std=c++20 in version 10. It also has an option to enable GNU extensions in addition to the experimental C++20 support, -std=gnu++20.

History

Changes applied to the C++20 working draft in July 2017 (Toronto) include:

  • concepts (what made it into the standard is a cut-down version; also described as "Concepts Lite")
  • designated initializers
  • [=, this] as a lambda capture
  • template parameter lists on lambdas
  • std::make_shared and std::allocate_shared for arrays

Changes applied to the C++20 working draft in the fall meeting in November 2017 (Albuquerque) include:

  • three-way comparison using the "spaceship operator", operator <=>
  • initialization of an additional variable within a range-based for statement
  • lambdas in unevaluated contexts
  • default constructible and assignable stateless lambdas
  • allow pack expansions in lambda init-capture
  • string literals as template parameters
  • atomic smart pointers (such as std::atomic<shared_ptr<T>> and std::atomic<weak_ptr<T>>)
  • std::to_address to convert a pointer to a raw pointer

Changes applied to the C++20 working draft in March 2018 (Jacksonville) include:

  • removing the need for typename in certain circumstances
  • new standard attributes [[no_unique_address]], [[likely]] and [[unlikely]]
  • calendar and time-zone additions to <chrono>
  • std::span, providing a view to a contiguous array (analogous to std::string_view but span can mutate the referenced sequence)
  • <version> header

Changes applied to the C++20 working draft in the summer meeting in June 2018 (Rapperswil) include:

  • contracts (later deferred to a later standard)
  • feature test macros
  • bit-casting of object representations, with less verbosity than memcpy() and more ability to exploit compiler internals
  • conditional explicit, allowing the explicit modifier to be contingent on a boolean expression
  • constexpr virtual functions

Changes applied to the C++20 working draft in the fall meeting in November 2018 (San Diego) include:

  • ranges (The One Ranges Proposal)
  • concept terse syntax
  • constexpr union, try and catch, dynamic_cast, typeid and std::pointer_traits.
  • various constexpr library bits
  • immediate functions using the new consteval keyword
  • signed integers are now defined to be represented using two's complement (signed integer overflow remains undefined behavior)
  • refinements of the contracts facility (access control in contract conditions) (see list of features deferred to a later standard)
  • a revised memory model
  • smart pointer creation with default initialization

Changes applied to the C++20 working draft in the winter meeting in February 2019 (Kona) include:

  • coroutines
  • modules
  • various improvements to structured bindings (interaction with lambda captures, static and thread_local storage duration)

Changes applied to the C++20 working draft in the summer meeting in July 2019 (Cologne) include:

  • contracts were removed (see list of features deferred to a later standard)
  • use of comma operator in subscript expressions has been deprecated
  • constexpr additions (trivial default initialization, unevaluated inline-assembly)
  • using scoped enums
  • various changes to the spaceship operator
  • DR: minor changes to modules
  • constinit keyword
  • changes to concepts (removal of -> Type return-type-requirements)
  • (most of) volatile has been deprecated
  • DR: [[nodiscard]] effects on constructors
  • The new standard library concepts will not use PascalCase (rather standard_case, as the rest of the standard library)
  • text formatting (std::formatchrono integration, corner case fixes)
  • bit operations
  • constexpr INVOKE
  • math constants
  • consistency additions to atomics (std::atomic_ref<T>, std::atomic<std::shared_ptr<T>>)
  • add the <=> operator to the standard library
  • header units for the standard library
  • synchronization facilities (merged from: Efficient atomic waiting and semaphores, latches and barriers, Improving atomic_flag, Don't Make C++ Unimplementable On Small CPUs)
  • std::source_location
  • constexpr containers (std::string, std::vector)
  • std::stop_token and joining thread (std::jthread)

Changes applied during the NB comment resolution in the fall meeting in November 2019 (Belfast) include:

  • Class Types in Non-Type Template Parameters (NTTP): The restriction of no user-defined operator== allowed has been removed as the meaning of template argument equality has been divorced from operator==. This allows also for array members in class-type NTTP.
  • Floating-point types, pointers and references and unions and union-like classes (class types containing anonymous unions) are now allowed as NTTP.
  • Function identity now also includes trailing requires-clauses (P1971)
  • Constrained non-template functions have been removed
  • <compare> is now available in freestanding implementations
  • std::spans typedef was changed from index_type to size_type to be consistent with the rest of the standard library
  • Concept traits have been renamed to follow the renaming of the concepts as a result from the Cologne meeting
  • Several fixes and additions to ranges (P1456R1: Move-only views, P1391R4: Range constructor for std::string_view (constructor from iterator-pair of characters), P1394R4: Range constructor for std::span<ref>, P1870R1: forwarding-range<T> is too subtle)
  • Initialization for std::atomic<T> has been changed to make it work with default and list initialization, std::latch and std::barrier can now report the maximum number of threads that the implementation supports through the new member function max()
  • std::weak_equality and std::strong_equality have been removed as they are not used anymore
  • Algorithms in <numeric> have been made constexpr
  • Missing feature-test macros for new or changed features of C++20 have been added

Inequality (mathematics)

From Wikipedia, the free encyclopedia https://en.wikipedia.org/wiki/Inequality...