Add isSameSymbol and isSameType to v3 traits and indexOf to v3 meta. (#8960)

indexOf is phobos.sys.meta's version of std.meta's staticIndexOf.

The name was changed to better match our naming scheme. Because the
result of the template is a value and not a type or AliasSeq, it is
correct for it to be camelCased, but we don't normally prepend templates
with static, making it inconsistent to do so in the case of indexOf.

This might result in some symbol conflicts with indexOf from the
algorithm modules (assuming that we retain that function and its name
for Phobos v3), but a quick test showed that importing both
phobos.sys.meta and std.algorithm didn't result in a symbol conflict
when using the one from phobos.sys.meta. And even if we do get symbol
conflicts in some situations, the module system is designed to allow us
to deal with that.

As for the implementation, I've both made indexOf more flexible and more
straightforward.

staticIndexOf looks for an element in the AliasSeq which is "the same"
as the first argument, and that results in a big mess, since what "the
same" is varies considerably depending on what the elements are, and
staticIndexOf makes it even more complicated by evaluating some symbols
at CTFE if it can (e.g. evaluating a no-arg function that returns an int
to try to compare it to an integer literal) while not evaluating them in
other cases. Not only did trying to document the current behavior make
it clear that it's just way too confusing, but even trying to come up
with a sane simplification of how the comparison works was just too
messy, because it's trying to be able to compare just about anything
which you can stick in an AliasSeq. So, I punted on it by taking the
comparison out of the equation entirely.

indexOf now takes a template predicate. So, rather than looking for an
element which is the same, it looks for an element which matches the
predicate. This allows indexOf to be used in more cases than
staticIndexOf can be, _and_ it allows the programmer using it to decide
how the comparison works by choosing a predicate that matches what they
want.

So, in conjuction with that I added isSameSymbol and isSameType to
phobos.sys.traits, since those should correspond to the most common
searches that someone would be trying to do with staticIndexOf, but
since those traits are very specific in what they search for rather than
searching for an element which is "the same" in some nebulous sense, the
code should end up being much clearer and cleaner. And if someone wants
to do something completely different like indexOf!(isInteger, Types),
then they can, unlike with staticIndexOf.
This commit is contained in:
Jonathan M Davis 2024-03-27 03:33:54 -06:00 committed by GitHub
parent a613e8f714
commit ec0857574c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 209 additions and 1 deletions

View file

@ -63,6 +63,7 @@
$(TR $(TD Alias sequence searching) $(TD
$(LREF all)
$(LREF any)
$(LREF indexOf)
))
)
@ -393,3 +394,81 @@ else
static assert(!any!isInteger);
}
/++
Returns the index of the first element where $(D Pred!(Args[i])) is
$(D true).
If $(D Pred!(Args[i])) is not $(D true) for any elements, then the result
is $(D -1).
Evaluation is $(I not) short-circuited if a $(D true) result is
encountered; the template predicate must be instantiable with all the
elements.
+/
template indexOf(alias Pred, Args...)
{
enum ptrdiff_t indexOf =
{
static foreach (i; 0 .. Args.length)
{
static if (Pred!(Args[i]))
return i;
}
return -1;
}();
}
///
@safe unittest
{
import phobos.sys.traits : isInteger, isSameSymbol, isSameType;
alias Types1 = AliasSeq!(string, int, long, char[], ubyte, int);
alias Types2 = AliasSeq!(float, double, int[], char[], void);
static assert(indexOf!(isInteger, Types1) == 1);
static assert(indexOf!(isInteger, Types2) == -1);
static assert(indexOf!(isSameType!ubyte, Types1) == 4);
static assert(indexOf!(isSameType!ubyte, Types2) == -1);
int i;
int j;
string s;
int foo() { return 0; }
alias Symbols = AliasSeq!(i, j, foo);
static assert(indexOf!(isSameSymbol!j, Symbols) == 1);
static assert(indexOf!(isSameSymbol!s, Symbols) == -1);
// Empty AliasSeq.
static assert(indexOf!isInteger == -1);
// The predicate does not compile with all of the arguments,
// so indexOf does not compile.
static assert(!__traits(compiles, indexOf!(isSameType!int, long, int, 42)));
}
unittest
{
import phobos.sys.traits : isSameType;
static assert(indexOf!(isSameType!int, short, int, long) >= 0);
static assert(indexOf!(isSameType!string, short, int, long) < 0);
// This is to verify that we don't accidentally end up with the type of
// the result differing based on whether it's -1 or not. Not specifying the
// type at all in indexOf results in -1 being int on all systems and the
// other results being whatever size_t is (ulong on most systems at this
// point), which does generally work, but being explicit with the type
// avoids any subtle issues that might come from the type of the result
// varying based on whether the item is found or not.
static assert(is(typeof(indexOf!(isSameType!int, short, int, long)) ==
typeof(indexOf!(isSameType!string, short, int, long))));
static assert(indexOf!(isSameType!string, string, string, string, string) == 0);
static assert(indexOf!(isSameType!string, int, string, string, string) == 1);
static assert(indexOf!(isSameType!string, int, int, string, string) == 2);
static assert(indexOf!(isSameType!string, int, int, int, string) == 3);
static assert(indexOf!(isSameType!string, int, int, int, int) == -1);
}

View file

@ -51,7 +51,6 @@
$(BOOKTABLE ,
$(TR $(TH Category) $(TH Templates))
$(TR $(TD Categories of types) $(TD
$(TR $(TD Traits for removing type qualfiers) $(TD
$(LREF isDynamicArray)
$(LREF isFloatingPoint)
$(LREF isInteger)
@ -65,6 +64,10 @@
$(LREF isImplicitlyConvertible)
$(LREF isQualifierConvertible)
))
$(TR $(TD Traits for comparisons) $(TD
$(LREF isSameSymbol)
$(LREF isSameType)
))
$(TR $(TD Traits for removing type qualfiers) $(TD
$(LREF Unconst)
$(LREF Unshared)
@ -1702,6 +1705,132 @@ enum isQualifierConvertible(From, To) = is(immutable From == immutable To) && is
}
}
/++
Whether the given symbols are the same symbol.
All this does is $(D __traits(isSame, lhs, rhs)), so most code shouldn't
use it. It's intended to be used in conjunction with templates that take a
template predicate - such as those in phobos.sys.meta.
The single-argument overload makes it so that it can be partially
instantiated with the first argument, which will often be necessary with
template predicates.
See_Also:
$(DDSUBLINK spec/traits, isSame, $(D __traits(isSame, lhs, rhs)))
$(LREF isSameType)
+/
enum isSameSymbol(alias lhs, alias rhs) = __traits(isSame, lhs, rhs);
/++ Ditto +/
template isSameSymbol(alias lhs)
{
enum isSameSymbol(alias rhs) = __traits(isSame, lhs, rhs);
}
///
@safe unittest
{
int i;
int j;
real r;
static assert( isSameSymbol!(i, i));
static assert(!isSameSymbol!(i, j));
static assert(!isSameSymbol!(i, r));
static assert(!isSameSymbol!(j, i));
static assert( isSameSymbol!(j, j));
static assert(!isSameSymbol!(j, r));
static assert(!isSameSymbol!(r, i));
static assert(!isSameSymbol!(r, j));
static assert( isSameSymbol!(r, r));
auto foo() { return 0; }
auto bar() { return 0; }
static assert( isSameSymbol!(foo, foo));
static assert(!isSameSymbol!(foo, bar));
static assert(!isSameSymbol!(foo, i));
static assert(!isSameSymbol!(bar, foo));
static assert( isSameSymbol!(bar, bar));
static assert(!isSameSymbol!(bar, i));
// Types are symbols too. However, in most cases, they should be compared
// as types, not symbols (be it with is expressions or with isSameType),
// because the results aren't consistent between scalar types and
// user-defined types with regards to type qualifiers when they're compared
// as symbols.
static assert( isSameSymbol!(double, double));
static assert(!isSameSymbol!(double, const double));
static assert(!isSameSymbol!(double, int));
static assert( isSameSymbol!(Object, Object));
static assert( isSameSymbol!(Object, const Object));
static assert(!isSameSymbol!(i, int));
static assert( isSameSymbol!(typeof(i), int));
// Lambdas can be compared with __traits(isSame, ...),
// so they can be compared with isSameSymbol.
static assert( isSameSymbol!(a => a + 42, a => a + 42));
static assert(!isSameSymbol!(a => a + 42, a => a + 99));
// Partial instantiation allows it to be used with templates that expect
// a predicate that takes only a single argument.
import phobos.sys.meta : AliasSeq, indexOf;
alias Types = AliasSeq!(i, j, r, int, long, foo);
static assert(indexOf!(isSameSymbol!j, Types) == 1);
static assert(indexOf!(isSameSymbol!int, Types) == 3);
static assert(indexOf!(isSameSymbol!bar, Types) == -1);
}
/++
Whether the given types are the same type.
All this does is $(D is(T == U)), so most code shouldn't use it. It's
intended to be used in conjunction with templates that take a template
predicate - such as those in phobos.sys.meta.
The single-argument overload makes it so that it can be partially
instantiated with the first argument, which will often be necessary with
template predicates.
See_Also:
$(LREF isSameSymbol)
+/
enum isSameType(T, U) = is(T == U);
/++ Ditto +/
template isSameType(T)
{
enum isSameType(U) = is(T == U);
}
///
@safe unittest
{
static assert( isSameType!(long, long));
static assert(!isSameType!(long, const long));
static assert(!isSameType!(long, string));
static assert( isSameType!(string, string));
int i;
real r;
static assert( isSameType!(int, typeof(i)));
static assert(!isSameType!(int, typeof(r)));
static assert(!isSameType!(real, typeof(i)));
static assert( isSameType!(real, typeof(r)));
// Partial instantiation allows it to be used with templates that expect
// a predicate that takes only a single argument.
import phobos.sys.meta : AliasSeq, indexOf;
alias Types = AliasSeq!(float, string, int, double);
static assert(indexOf!(isSameType!int, Types) == 2);
}
/++
Removes the outer layer of $(D const), $(D inout), or $(D immutable)
from type $(D T).