Enums, Namespaces, Destructuring & Friends
The smaller pieces of the language: enums, namespaces and using, destructuring, the conditional operator and null-safe access. Each works as a C++ developer would guess; the differences are stated.
Enums
An enum is a type. enum Dir { north, south, east, west } declares Dir and its values, written Dir::south (the dot form Dir.south names the same value).
- A
Dirslot (variable, parameter, return, field, element, map key or value) holds onlyDirvalues and starts at the first one.auto d = Dir::north;is aDir. - Enum to int is implicit:
int n = d + 1;. - Int to enum needs
cast<Dir>(n), which raises for a number the enum lacks. A bare int or another enum's value into aDirslot is a load error, and so ared += 1andd++. - A
varholds the integer:type_ofreports"int", and it enters aDirslot when it is one of the values. print,to_string,format,to_jsonandto_binaryshow or write the integer; a load restores the enum.- Overloads tell
f(Dir)fromf(int),catch (Dir e)takes a thrownDir, and a map literal takes values as keys. - A value that does not exist fails the load:
no value 'zzz' in enum 'Dir'.
enum Dir { north, south, east, west }
Dir d = Dir::south;
int n = d + 1; // enum to int: implicit
string name(Dir where) { return where == Dir::east ? "east" : "elsewhere"; }
print("{} {} {} {}", d, type_of(d), n, name(cast<Dir>(n)));
switch (d) {
case Dir::north: print("up");
case Dir::south: print("down");
default: print("sideways");
}
map<Dir, string> short_names = {Dir::north: "N", Dir::south: "S"};
var v = d;
print("{} {}", short_names[d], type_of(v));
try { d = cast<Dir>(9); } catch (e) { print("caught: {}", e); }1 Dir 2 east
down
S int
caught: Cannot cast 9 to 'Dir': not one of its valuesenum Dir { north, south, east, west }
Dir d = 2;jaiscript: static check failed:
<script>:2:5: error: cannot initialize 'd' declared 'Dir' with 'int': cast<Dir>(n) converts an int that is one of its values
Dir d = 2;
^Values. Values count from 0. A value may take a constant integer initializer built from integer and char literals, the enum's earlier values and the integer operators; a value without one is the one before it plus one, and two values may share a number. A name that is not an earlier value, a shift outside 0 to 63 or an overflow fails the load.
The value table. The enum's name alone, used as a value, is a map<string,int> from each value's name to its number, walked in key order. An int slot refuses it at load (an enum's name is its value table, a map<string,int>; name one of its values (Dir::north)).
enum Flag { none, read = 1 << 0, write = 1 << 1, exec = 1 << 2, all = read | write | exec, next }
print("{} {} {} {}", Flag::write, Flag::all, Flag::next, Flag::all & Flag::exec);
for (auto [name, n] : Flag) { print("{} = {}", name, n); }2 7 8 4
all = 7
exec = 4
next = 8
none = 0
read = 1
write = 2An enum declared inside a function or block is local to it. A program-scope enum of the same name keeps its values, and the checker warns when a local one hides it.
Namespaces
namespace name { ... } groups functions, variables, classes and enums behind name::. Blocks reopen: every namespace utils { } in the program, in any file, adds to the one namespace. namespace a::b { } and a block nested in namespace a declare the same a::b.
int limit = 100; // a global of the same name stays separate
namespace game {
int limit = 10;
int bonus = limit * 2; // an initializer sees the members above it
int scale(int v) { return clamp_to(v * 2); } // declared below: found when called
int clamp_to(int v) { return v > limit ? limit : v; } // game::limit hides the global
int global_limit() { return ::limit; } // ::name reaches the global
class Unit { int hp = 5; }
namespace ai {
int think(int v) { return scale(v) + 1; } // from the enclosing namespace
}
}
game::limit += 1;
game::Unit u;
print("{} {} {} {} {}", game::scale(7), game::ai::think(2), game::global_limit(), game::bonus, type_of(u));
try { string::to_lower = 3; } catch (e) { print("caught: {}", e); }11 5 100 20 game::Unit
caught: Cannot assign to 'to_lower': it is read-onlyBare names. Code in a namespace (its functions, their lambdas and coroutines, its classes' methods and field initializers) looks a bare name up in this order: locals, the namespace's own members, each enclosing namespace innermost first, then globals. The lookup happens when the code runs, so a function declared later, in a reopening or by a reload is found. Outside the namespace a member's bare name is use of undeclared identifier.
Rules.
- A namespace variable is one variable;
ns::x = vstores under its declared type. The namespace's top-level code runs in order, as a file's does (top-level order). - Functions overload as anywhere (Functions). Redefining an existing signature needs
override:Function 'f' already exists in namespace 'n'. Use 'override' keyword to replace it. - A class declared in a namespace is named
ns::Boxfrom every file; that is whattype_ofreports and a saved$typecarries. Inside the namespace the bareBoxworks. Two namespaces that each declare aBoxdeclare two classes. - A namespace may share a class's name. Its functions replace the class's static methods of matching arity when they say
override; without it the collision is an error. - A qualified name the namespace does not declare fails the load:
no member 'sqroot' in namespace 'math'. Every declaration counts (any file, an earlier run, the host), so a script'snamespace math { }adds tomath::. A file with a dynamic-path include gets a warning instead. - The host's entries (
string::to_lower,time::now) are read-only. - Any reserved word but
thisandsupernames a namespace member or an enum value, reached qualified:spider::catch(url),Mode::default(keyword member names).
using ns::name;
using geo::twice, geo::Pt; makes each name stand for its qualified form in the rest of that file, as a type, a call or a value. There is no using namespace. It is a rewrite of that file's text: files that include or import this one do not inherit it, a name after ., ?., :: or -> is left alone, and declaring the name again in the file is a parse error.
namespace geo {
float twice(float v) { return v * 2.0; }
class Pt { int x = 1; int y = 2; }
}
using geo::twice, geo::Pt;
Pt p;
print("{} {}", twice(3.0), p.x + p.y);6.0 3Destructuring
auto [a, b, c] = arr; unpacks an array by position. Extra elements are ignored and missing ones bind null. In a range-for over a map, auto [key, value] binds each entry.
auto [x, y] = [10, 20, 30];
auto [p, q, r] = [1, 2];
print("{} {} {} {} {}", x, y, p, q, r);
map<string, int> stock = {"ore": 4, "gem": 1};
for (auto [name, count] : stock) { print("{}: {}", name, count); }10 20 1 2 null
gem: 1
ore: 4The conditional operator
c ? a : b is right-associative, so a cascade reads top-down without parentheses: hp > 75 ? "healthy" : hp > 25 ? "wounded" : "critical". With an int arm and a float arm the result is a float. When both arms are lvalues of one type the whole expression is one (Value Semantics).
Null-safe access: ?.
x?.member and x?.method(...) are null when x is null, and the call's arguments are not evaluated. A null in front of a ?. skips the rest of the chain, as in JavaScript and C#. Only the value in front of each ?. is tested, so put one on every link that may be null. ?? supplies a default (Operators).
class Node { int v = 3; shared_ptr<Node> next; }
auto head = new Node();
shared_ptr<Node> none;
print("{} {} {} {}", head?.v, head?.next?.v, none?.next.v, none?.v ?? -1);
try { print(head?.next.v); } catch (e) { print("caught: {}", e); }3 null null -1
caught: Cannot access 'v' on null ('next' is null)