alias
and with
alias
The alias
keyword assigns aliases to existing names. alias
is different from and unrelated to alias this
.
Shortening a long name
As we have encountered in the previous chapter, some names may become too long to be convenient. Let's consider the following function from that chapter:
Stack!(Point!double) randomPoints(size_t count) { auto points = new Stack!(Point!double); // ... }
Having to type Stack!(Point!double)
explicitly in multiple places in the program has a number of drawbacks:
- Longer names can make the code harder to read.
- It is unnecessary to be reminded at every point that the type is the
Stack
data structure that contains objects of thedouble
instantiations of thePoint
struct template. - If the requirements of the program change and e.g.
double
needs to be changed toreal
, this change must be carried out in multiple places.
These drawbacks can be eliminated by giving a new name to Stack!(Point!double)
:
alias Points = Stack!(Point!double); // ... Points randomPoints(size_t count) { auto points = new Points; // ... }
It may make sense to go further and define two aliases, one taking advantage of the other:
alias PrecisePoint = Point!double; alias Points = Stack!PrecisePoint;
The syntax of alias
is the following:
alias new_name = existing_name;
After that definition, the new name and the existing name become synonymous: They mean the same thing in the program.
You may encounter the older syntax of this feature in some programs:
// Use of old syntax is discouraged: alias existing_name new_name;
alias
is also useful when shortening names which otherwise need to be spelled out along with their module names. Let's assume that the name Queen
appears in two separate modules: chess
and palace
. When both modules are imported, typing merely Queen
would cause a compilation error:
import chess; import palace; // ... Queen person; // ← compilation ERROR
The compiler cannot decide which Queen
has been meant:
Error: chess.Queen at chess.d(1) conflicts with palace.Queen at palace.d(1)
A convenient way of resolving this conflict is to assign aliases to one or more of the names:
import palace; alias PalaceQueen = palace.Queen; void main() { PalaceQueen person; // ... PalaceQueen anotherPerson; }
alias
works with other names as well. The following code gives a new name to a variable:
int variableWithALongName = 42; alias var = variableWithALongName; var = 43; assert(variableWithALongName == 43);
Design flexibility
For flexibility, even fundamental types like int
can have aliases:
alias CustomerNumber = int; alias CompanyName = string; // ... struct Customer { CustomerNumber number; CompanyName company; // ... }
If the users of this struct always type CustomerNumber
and CompanyName
instead of int
and string
, then the design can be changed in the future to some extent, without affecting user code.
This helps with the readability of code as well. Having the type of a variable as CustomerNumber
conveys more information about the meaning of that variable than int
.
Sometimes such type aliases are defined inside structs and classes and become parts of the interfaces of those types. The following class has a weight
property:
class Box { private: double weight_; public: double weight() const { return weight_; } // ... }
Because the member variable and the property of that class is defined as double
, the users would have to use double
as well:
double totalWeight = 0; foreach (box; boxes) { totalWeight += box.weight; }
Let's compare it to another design where the type of weight
is defined as an alias
:
class Box { private: Weight weight_; public: alias Weight = double; Weight weight() const { return weight_; } // ... }
Now the user code would normally use Weight
as well:
Box.Weight totalWeight = 0; foreach (box; boxes) { totalWeight += box.weight; }
With this design, changing the actual type of Weight
in the future would not affect user code. (That is, if the new type supports the +=
operator as well.)
Revealing hidden names of superclasses
When the same name appears both in the superclass and in the subclass, the matching names that are in the superclass are hidden. Even a single name in the subclass is sufficient to hide all of the names of the superclass that match that name:
class Super { void foo(int x) { // ... } } class Sub : Super { void foo() { // ... } } void main() { auto object = new Sub; object.foo(42); // ← compilation ERROR }
Since the argument is 42, an int
value, one might expect that the Super.foo
function that takes an int
would be called for that use. However, even though their parameter lists are different, Sub.foo
hides Super.foo
and causes a compilation error. The compiler disregards Super.foo
altogether and reports that Sub.foo
cannot be called by an int
:
Error: function deneme.Sub.foo () is not callable using argument types (int)
Note that this is not the same as overriding a function of the superclass. For that, the function signatures would be the same and the function would be overridden by the override
keyword. (The override
keyword has been explained in the Inheritance chapter.)
Here, not overriding, but a language feature called name hiding is in effect. If there were not name hiding, functions that happen to have the same name foo
that are added to or removed from these classes might silently change the function that would get called. Name hiding prevents such surprises. It is a feature of other OOP languages as well.
alias
can reveal the hidden names when desired:
class Super { void foo(int x) { // ... } } class Sub : Super { void foo() { // ... } alias foo = Super.foo; }
The alias
above brings the foo
names from the superclass into the subclass interface. As a result, the code now compiles and Super.foo
gets called.
When it is more appropriate, it is possible to bring the names under a different name as well:
class Super { void foo(int x) { // ... } } class Sub : Super { void foo() { // ... } alias generalFoo = Super.foo; } // ... void main() { auto object = new Sub; object.generalFoo(42); }
Name hiding affects member variables as well. alias
can bring those names to the subclass interface as well:
class Super { int city; } class Sub : Super { string city() const { return "Kayseri"; } }
Regardless of one being a member variable and the other a member function, the name city
of the subclass hides the name city
of the superclass:
void main() { auto object = new Sub; object.city = 42; // ← compilation ERROR }
Similarly, the names of the member variables of the superclass can be brought to the subclass interface by alias
, possibly under a different name:
class Super { int city; } class Sub : Super { string city() const { return "Kayseri"; } alias cityCode = Super.city; } void main() { auto object = new Sub; object.cityCode = 42; }
with
with
is for removing repeated references to an object or symbol. It takes an expression or a symbol in parentheses and uses that expression or symbol when looking up other symbols that are used inside the scope of with
:
struct S { int i; int j; } void main() { auto s = S(); with (s) { i = 1; // means s.i j = 2; // means s.j } }
It is possible to create a temporary object inside the parentheses. In that case, the temporary object becomes an lvalue, lifetime of which ends upon leaving the scope:
with (S()) { i = 1; // the i member of the temporary object j = 2; // the j member of the temporary object }
As we will see later in the Pointers chapter, it is possible to construct the temporary object with the new
keyword, in which case its lifetime can be extended beyond the scope.
with
is especially useful with case
sections for removing repeated references to e.g. an enum
type:
enum Color { red, orange } // ... final switch (c) with (Color) { case red: // means Color.red // ... case orange: // means Color.orange // ... }
Summary
alias
assigns aliases to existing names.with
removes repeated references to the same object or symbol.