Skip to content

Commit 23e5d21

Browse files
authored
Add isEqual and EnumMembers to phobos.sys.traits. (#8970)
EnumMembers is obviously needed and does the same thing as its std.traits counterpart. isEqual is new, and it really shouldn't be used in many circumstances, but it's needed in conjunction with Unique to be able to do what NoDuplicates from std.meta does when given an AliasSeq of enum members. So, if you need the list of enum members to have no duplicate values (e.g. when creating a final switch), then you would now do Unique!(isEqual, EnumMembers!E) instead of NoDuplicates!(EnumMembers!E). As part of isEqual's documentation, I added a list of examples which highlight the difference between operating on the list of enum members as an AliasSeq and operating on them as a dynamic array, since that's not something that's at all obvious - and it shows why you might need to use isEqual with Unique to weed out duplicate values instead of doing something like [EnumMembers!E].sort().unique() to weed them out. For documentation purposes, I just assumed that uniq would be renamed to unique, but the documentation can be fixed later if need be once we have the actual functions in Phobos v3.
1 parent 77adcad commit 23e5d21

File tree

1 file changed

+312
-0
lines changed

1 file changed

+312
-0
lines changed

phobos/sys/traits.d

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@
6060
$(LREF isStaticArray)
6161
$(LREF isUnsignedInteger)
6262
))
63+
$(TR $(TD Aggregate Type traits) $(TD
64+
$(LREF EnumMembers)
65+
))
6366
$(TR $(TD Traits testing for type conversions) $(TD
6467
$(LREF isImplicitlyConvertible)
6568
$(LREF isQualifierConvertible)
6669
))
6770
$(TR $(TD Traits for comparisons) $(TD
71+
$(LREF isEqual)
6872
$(LREF isSameSymbol)
6973
$(LREF isSameType)
7074
))
@@ -1296,6 +1300,172 @@ enum isPointer(T) = is(T == U*, U);
12961300
}
12971301
}
12981302

1303+
/++
1304+
Evaluates to an $(D AliasSeq) containing the members of an enum type.
1305+
1306+
The elements of the $(D AliasSeq) are in the same order as they are in the
1307+
enum declaration.
1308+
1309+
An enum can have multiple members with the same value, so if code needs the
1310+
enum values to be unique (e.g. if it's generating a switch statement from
1311+
them), then $(REF Unique, phobos, sys, meta) can be used to filter out the
1312+
duplicate values - e.g. $(D Unique!(isEqual, EnumMembers!E)).
1313+
+/
1314+
template EnumMembers(E)
1315+
if (is(E == enum))
1316+
{
1317+
import phobos.sys.meta : AliasSeq;
1318+
1319+
alias EnumMembers = AliasSeq!();
1320+
static foreach (member; __traits(allMembers, E))
1321+
EnumMembers = AliasSeq!(EnumMembers, __traits(getMember, E, member));
1322+
}
1323+
1324+
/// Create an array of enum values.
1325+
@safe unittest
1326+
{
1327+
enum Sqrts : real
1328+
{
1329+
one = 1,
1330+
two = 1.41421,
1331+
three = 1.73205
1332+
}
1333+
auto sqrts = [EnumMembers!Sqrts];
1334+
assert(sqrts == [Sqrts.one, Sqrts.two, Sqrts.three]);
1335+
}
1336+
1337+
/++
1338+
A generic function $(D rank(v)) in the following example uses this template
1339+
for finding a member $(D e) in an enum type $(D E).
1340+
+/
1341+
@safe unittest
1342+
{
1343+
// Returns i if e is the i-th member of E.
1344+
static size_t rank(E)(E e)
1345+
if (is(E == enum))
1346+
{
1347+
static foreach (i, member; EnumMembers!E)
1348+
{
1349+
if (e == member)
1350+
return i;
1351+
}
1352+
assert(0, "Not an enum member");
1353+
}
1354+
1355+
enum Mode
1356+
{
1357+
read = 1,
1358+
write = 2,
1359+
map = 4
1360+
}
1361+
assert(rank(Mode.read) == 0);
1362+
assert(rank(Mode.write) == 1);
1363+
assert(rank(Mode.map) == 2);
1364+
}
1365+
1366+
/// Use EnumMembers to generate a switch statement using static foreach.
1367+
@safe unittest
1368+
{
1369+
static class Foo
1370+
{
1371+
string calledMethod;
1372+
void foo() @safe { calledMethod = "foo"; }
1373+
void bar() @safe { calledMethod = "bar"; }
1374+
void baz() @safe { calledMethod = "baz"; }
1375+
}
1376+
1377+
enum FuncName : string { foo = "foo", bar = "bar", baz = "baz" }
1378+
1379+
auto foo = new Foo;
1380+
1381+
s: final switch (FuncName.bar)
1382+
{
1383+
static foreach (member; EnumMembers!FuncName)
1384+
{
1385+
// Generate a case for each enum value.
1386+
case member:
1387+
{
1388+
// Call foo.{enum value}().
1389+
__traits(getMember, foo, member)();
1390+
break s;
1391+
}
1392+
}
1393+
}
1394+
1395+
// Since we passed FuncName.bar to the switch statement, the bar member
1396+
// function was called.
1397+
assert(foo.calledMethod == "bar");
1398+
}
1399+
1400+
@safe unittest
1401+
{
1402+
{
1403+
enum A { a }
1404+
static assert([EnumMembers!A] == [A.a]);
1405+
enum B { a, b, c, d, e }
1406+
static assert([EnumMembers!B] == [B.a, B.b, B.c, B.d, B.e]);
1407+
}
1408+
{
1409+
enum A : string { a = "alpha", b = "beta" }
1410+
static assert([EnumMembers!A] == [A.a, A.b]);
1411+
1412+
static struct S
1413+
{
1414+
int value;
1415+
int opCmp(S rhs) const nothrow { return value - rhs.value; }
1416+
}
1417+
enum B : S { a = S(1), b = S(2), c = S(3) }
1418+
static assert([EnumMembers!B] == [B.a, B.b, B.c]);
1419+
}
1420+
{
1421+
enum A { a = 0, b = 0, c = 1, d = 1, e }
1422+
static assert([EnumMembers!A] == [A.a, A.b, A.c, A.d, A.e]);
1423+
}
1424+
{
1425+
enum E { member, a = 0, b = 0 }
1426+
1427+
static assert(__traits(isSame, EnumMembers!E[0], E.member));
1428+
static assert(__traits(isSame, EnumMembers!E[1], E.a));
1429+
static assert(__traits(isSame, EnumMembers!E[2], E.b));
1430+
1431+
static assert(__traits(identifier, EnumMembers!E[0]) == "member");
1432+
static assert(__traits(identifier, EnumMembers!E[1]) == "a");
1433+
static assert(__traits(identifier, EnumMembers!E[2]) == "b");
1434+
}
1435+
}
1436+
1437+
// https://issues.dlang.org/show_bug.cgi?id=14561: huge enums
1438+
@safe unittest
1439+
{
1440+
static string genEnum()
1441+
{
1442+
string result = "enum TLAs {";
1443+
foreach (c0; '0' .. '2' + 1)
1444+
{
1445+
foreach (c1; '0' .. '9' + 1)
1446+
{
1447+
foreach (c2; '0' .. '9' + 1)
1448+
{
1449+
foreach (c3; '0' .. '9' + 1)
1450+
{
1451+
result ~= '_';
1452+
result ~= c0;
1453+
result ~= c1;
1454+
result ~= c2;
1455+
result ~= c3;
1456+
result ~= ',';
1457+
}
1458+
}
1459+
}
1460+
}
1461+
result ~= '}';
1462+
return result;
1463+
}
1464+
mixin(genEnum);
1465+
static assert(EnumMembers!TLAs[0] == TLAs._0000);
1466+
static assert(EnumMembers!TLAs[$ - 1] == TLAs._2999);
1467+
}
1468+
12991469
/++
13001470
Whether the type $(D From) is implicitly convertible to the type $(D To).
13011471
@@ -1705,6 +1875,146 @@ enum isQualifierConvertible(From, To) = is(immutable From == immutable To) && is
17051875
}
17061876
}
17071877

1878+
/++
1879+
Whether the given values are equal per $(D ==).
1880+
1881+
All this does is $(D lhs == rhs) but in an eponymous template, so most code
1882+
shouldn't use it. It's intended to be used in conjunction with templates
1883+
that take a template predicate - such as those in phobos.sys.meta.
1884+
1885+
The single-argument overload makes it so that it can be partially
1886+
instantiated with the first argument, which will often be necessary with
1887+
template predicates.
1888+
1889+
Note that in most cases, even when comparing values at compile time, using
1890+
isEqual makes no sense, because you can use CTFE to just compare two values
1891+
(or expressions which evaluate to values), but in rare cases where you need
1892+
to compare symbols in an $(D AliasSeq) by value with a template predicate
1893+
while still leaving them as symbols in an $(D AliasSeq), then isEqual would
1894+
be needed.
1895+
1896+
A prime example of this would be $(D Unique!(isEqual, EnumMembers!MyEnum)),
1897+
which results in an $(D AliasSeq) containing the list of members of
1898+
$(D MyEnum) but without any duplicate values (e.g. to use when doing code
1899+
generation to create a final switch).
1900+
1901+
Alternatively, code such as $(D [EnumMembers!MyEnum].sort().unique()) could
1902+
be used to get a dynamic array of the enum members with no duplicate values
1903+
via CTFE, thus avoiding the need for template predicates or anything from
1904+
phobos.sys.meta. However, you then have a dynamic array of enum values
1905+
rather than an $(D AliasSeq) of symbols for those enum members, which
1906+
affects what you can do with type introspection. So, which approach is
1907+
better depends on what the code needs to do with the enum members.
1908+
1909+
In general, however, if code doesn't need an $(D AliasSeq), and an array of
1910+
values will do the trick, then it's more efficient to operate on an array of
1911+
values with CTFE and avoid using isEqual or other templates to operate on
1912+
the values as an $(D AliasSeq).
1913+
1914+
See_Also:
1915+
$(LREF isSameSymbol)
1916+
$(LREF isSameType)
1917+
+/
1918+
enum isEqual(alias lhs, alias rhs) = lhs == rhs;
1919+
1920+
/++ Ditto +/
1921+
template isEqual(alias lhs)
1922+
{
1923+
enum isEqual(alias rhs) = lhs == rhs;
1924+
}
1925+
1926+
/// It acts just like ==, but it's a template.
1927+
@safe unittest
1928+
{
1929+
enum a = 42;
1930+
1931+
static assert( isEqual!(a, 42));
1932+
static assert( isEqual!(20, 10 + 10));
1933+
1934+
static assert(!isEqual!(a, 120));
1935+
static assert(!isEqual!(77, 19 * 7 + 2));
1936+
1937+
// b cannot be read at compile time, so it won't work with isEqual.
1938+
int b = 99;
1939+
static assert(!__traits(compiles, isEqual!(b, 99)));
1940+
}
1941+
1942+
/++
1943+
Comparing some of the differences between an $(D AliasSeq) of enum members
1944+
and an array of enum values created from an $(D AliasSeq) of enum members.
1945+
+/
1946+
@safe unittest
1947+
{
1948+
import phobos.sys.meta : AliasSeq, Unique;
1949+
1950+
enum E
1951+
{
1952+
a = 0,
1953+
b = 22,
1954+
c = 33,
1955+
d = 0,
1956+
e = 256,
1957+
f = 33,
1958+
g = 7
1959+
}
1960+
1961+
alias uniqueMembers = Unique!(isEqual, EnumMembers!E);
1962+
static assert(uniqueMembers.length == 5);
1963+
1964+
static assert(__traits(isSame, uniqueMembers[0], E.a));
1965+
static assert(__traits(isSame, uniqueMembers[1], E.b));
1966+
static assert(__traits(isSame, uniqueMembers[2], E.c));
1967+
static assert(__traits(isSame, uniqueMembers[3], E.e));
1968+
static assert(__traits(isSame, uniqueMembers[4], E.g));
1969+
1970+
static assert(__traits(identifier, uniqueMembers[0]) == "a");
1971+
static assert(__traits(identifier, uniqueMembers[1]) == "b");
1972+
static assert(__traits(identifier, uniqueMembers[2]) == "c");
1973+
static assert(__traits(identifier, uniqueMembers[3]) == "e");
1974+
static assert(__traits(identifier, uniqueMembers[4]) == "g");
1975+
1976+
// Same value but different symbol.
1977+
static assert(uniqueMembers[0] == E.d);
1978+
static assert(!__traits(isSame, uniqueMembers[0], E.d));
1979+
1980+
// is expressions compare types, not symbols or values, and these AliasSeqs
1981+
// contain the list of symbols for the enum members, not types, so the is
1982+
// expression evaluates to false even though the symbols are the same.
1983+
static assert(!is(uniqueMembers == AliasSeq!(E.a, E.b, E.c, E.e, E.g)));
1984+
1985+
// Once the members are converted to an array, the types are the same, and
1986+
// the values are the same, but the symbols are not the same. Instead of
1987+
// being the symbols E.a, E.b, etc., they're just values with the type E
1988+
// which match the values of E.a, E.b, etc.
1989+
enum arr = [uniqueMembers];
1990+
static assert(is(typeof(arr) == E[]));
1991+
1992+
static assert(arr == [E.a, E.b, E.c, E.e, E.g]);
1993+
static assert(arr == [E.d, E.b, E.f, E.e, E.g]);
1994+
1995+
static assert(!__traits(isSame, arr[0], E.a));
1996+
static assert(!__traits(isSame, arr[1], E.b));
1997+
static assert(!__traits(isSame, arr[2], E.c));
1998+
static assert(!__traits(isSame, arr[3], E.e));
1999+
static assert(!__traits(isSame, arr[4], E.g));
2000+
2001+
// Since arr[0] is just a value of type E, it's no longer the symbol, E.a,
2002+
// even though its type is E, and its value is the same as that of E.a. And
2003+
// unlike the actual members of an enum, an element of an array does not
2004+
// have an identifier, so __traits(identifier, ...) doesn't work with it.
2005+
static assert(!__traits(compiles, __traits(identifier, arr[0])));
2006+
2007+
// Similarly, once an enum member from the AliasSeq is assigned to a
2008+
// variable, __traits(identifer, ...) operates on the variable, not the
2009+
// symbol from the AliasSeq or the value of the variable.
2010+
auto var = uniqueMembers[0];
2011+
static assert(__traits(identifier, var) == "var");
2012+
2013+
// The same with a manifest constant.
2014+
enum constant = uniqueMembers[0];
2015+
static assert(__traits(identifier, constant) == "constant");
2016+
}
2017+
17082018
/++
17092019
Whether the given symbols are the same symbol.
17102020
@@ -1718,6 +2028,7 @@ enum isQualifierConvertible(From, To) = is(immutable From == immutable To) && is
17182028
17192029
See_Also:
17202030
$(DDSUBLINK spec/traits, isSame, $(D __traits(isSame, lhs, rhs)))
2031+
$(LREF isEqual)
17212032
$(LREF isSameType)
17222033
+/
17232034
enum isSameSymbol(alias lhs, alias rhs) = __traits(isSame, lhs, rhs);
@@ -1798,6 +2109,7 @@ template isSameSymbol(alias lhs)
17982109
template predicates.
17992110
18002111
See_Also:
2112+
$(LREF isEqual)
18012113
$(LREF isSameSymbol)
18022114
+/
18032115
enum isSameType(T, U) = is(T == U);

0 commit comments

Comments
 (0)