struct S(int a, int b) { }
void fun(T)(T t) { }
I want fun
to work with S
only. What would the signature constraint look like?
I can't make fun
a member of S
, and with void fun(T)(T t) if(is(T : S)) { }
I get Error: struct t1.S(int a,int b) is used as a type
S
is not a type. It's a template for a type. S!(5, 4)
is a type. It's quite possible that different instantiations of S
generate completely different code, so the definition of S!(5, 4)
could be completely different from S!(2, 5)
. For instance, S
could be
struct S(int a, int b)
{
static if(a > 3)
string foo;
static if(b == 4)
int boz = 17;
else
float boz = 2.1;
}
Note that the number and types of the member variables differ such that you can't really use an S!(5, 4)
in place of an S!(2, 5)
. They might as well have been structs named U
and V
which weren't templatized at all for all of the relation that they really have to one another.
Now, different instantiations of a particular template are generally similar with regards to their API (or they probably wouldn't have been done with the same template), but from the compiler's perspective, they have no relation with one another. So, the normal way to handle it is to use constraints purely on the API of the type and not on its name or what template it was instantiated from.
So, if you expect S
to have the functions foo
, bar
, and foozle
, and you want your fun
to use those functions, then you'll construct a constraint that tests that the type that's given to fun
has those functions and that they work as expected. For instance
void fun(T)(T t)
if(is({ auto a = t.foo(); t.bar(a); int i = t.foozle("hello", 22);}))
{}
Then any type which has a function called foo
which returns a value, a function named bar
which may or may not return a value and which takes the result of foo
, and a function named foozle
which takes a string
and an int
and returns an int
will compile with fun
. So, fun
is far more flexible than if you had insisted on it taking only instantiations of S
. In most cases, such constraints are separated out into separate eponymous templates (e.g. isForwardRange
or isDynamicArray
) rather than putting raw code in an is expression
so that they're reusable (and more user friendly), but expressions like that are what such eponymous templates use internally.
Now, if you really insist on constraining fun
such that it only works with instantiations of S
, then there are two options that I'm aware of.
1. Add a declaration of some kind which S
always has and you don't expect any other type to have. For instance
struct S(int a, int b)
{
enum isS = true;
}
void fun(T)(T t)
if(is(typeof(T.isS)))
{}
Note that the actual value of the declaration doesn't matter (nor does its type). It's the simple fact that it exists that you're testing for.
2. The more elegant (but far less obvious solution) is to do this:
struct S(int a, int b)
{
}
void fun(T)(T t)
if(is(T u : S!(i, j), int i, int j))
{}
is
expressions have a tendancy to border on voodoo once they get very complicated, but the version with commas is precisely what you need. The T u
is the type that you're testing and an identifier; the : S!(i, j)
gives the template specialization that you want T
to be an instantiation of; and the rest is a TemplateParameterList
declaring the symbols which are used in the stuff to the left but which haven't previously been declared - in this case, i
and j
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With