www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Are templates with variadic value parameters possible?

reply Devin Hill <98devin gmail.com> writes:
Hi everyone,

I have a struct template which takes an integer n, and then has a 
constructor taking that many arguments of type long, which looks 
like:

struct Struct(int n) {
     this(long[n] nums...) { /* stuff */ }
}

This works and lets me change n for each instantiation, but I 
wanted to make a function to construct it to gain the benefit of 
not having to write e.g.

Struct!2(1, 2);    // 2 could technically be inferred from the 
number of arguments
Struct!3(1, 2, 3); // similar story

// instead, with a function
makeStruct!(1, 2, 3, etc...); // ideal scenario returning 
Struct!n for some n

I tried writing this:

auto makeStruct(long[] nums...)() {
     return Struct!(nums.length)(nums);
}

but sadly it seems that the syntax is not recognized, despite the 
fact that it can be used in the non-template parameter section. I 
have had to settle for a non-variadic version, which complicates 
the syntax a little bit:


// non-variadic; negatively affects calling syntax
auto makeStruct(long[] nums)() {
     return Struct!(nums.length)(nums);
}

makeStruct!([1, 2, 3]); // less appealing because of the extra 
brackets
makeStruct![1, 2, 3];   // apparently invalid, despite no obvious 
conflict


This started out because I just wanted a cleaner-looking 
constructor, so it's kind of nitpicky, but now I'm just 
interested in whether this is possible at all. Is there any way 
to achieve what I'm trying to do without dissecting an AliasSeq? 
In other words, can I get a type-safe variadic value template 
parameter without conditionals? If there's anything I've 
explained inadequately, just ask.

Thanks.
Jul 14 2016
parent reply Basile B. <b2.temp gmx.com> writes:
On Friday, 15 July 2016 at 03:43:49 UTC, Devin Hill wrote:
 Hi everyone,

 I have a struct template which takes an integer n, and then has 
 a constructor taking that many arguments of type long, which 
 looks like:

 struct Struct(int n) {
     this(long[n] nums...) { /* stuff */ }
 }

 This works and lets me change n for each instantiation, but I 
 wanted to make a function to construct it to gain the benefit 
 of not having to write e.g.

 Struct!2(1, 2);    // 2 could technically be inferred from the 
 number of arguments
 Struct!3(1, 2, 3); // similar story

 // instead, with a function
 makeStruct!(1, 2, 3, etc...); // ideal scenario returning 
 Struct!n for some n

 I tried writing this:

 auto makeStruct(long[] nums...)() {
     return Struct!(nums.length)(nums);
 }

 but sadly it seems that the syntax is not recognized, despite 
 the fact that it can be used in the non-template parameter 
 section. I have had to settle for a non-variadic version, which 
 complicates the syntax a little bit:


 // non-variadic; negatively affects calling syntax
 auto makeStruct(long[] nums)() {
     return Struct!(nums.length)(nums);
 }

 makeStruct!([1, 2, 3]); // less appealing because of the extra 
 brackets
 makeStruct![1, 2, 3];   // apparently invalid, despite no 
 obvious conflict


 This started out because I just wanted a cleaner-looking 
 constructor, so it's kind of nitpicky, but now I'm just 
 interested in whether this is possible at all. Is there any way 
 to achieve what I'm trying to do without dissecting an 
 AliasSeq? In other words, can I get a type-safe variadic value 
 template parameter without conditionals? If there's anything 
 I've explained inadequately, just ask.

 Thanks.
With D style variadics it works, you can build the array from the list and have a static array: ===== void foo(T...)(T t) { T[0][T.length] tt = [t]; // T[0] is the type writeln(tt); // [1,2,3] static assert(isStaticArray!(typeof(tt))); } void main(string[] args) { foo(1,2,3); } ===== Note that you should check that the elements of T[] have all the same type. It shouldn't be a big problem.
Jul 14 2016
parent reply Devin Hill <98devin gmail.com> writes:
On Friday, 15 July 2016 at 04:08:19 UTC, Basile B. wrote:
 With D style variadics it works, you can build the array from 
 the list and have a static array:

 =====
 void foo(T...)(T t)
 {
     T[0][T.length] tt = [t]; // T[0] is the type
     writeln(tt); // [1,2,3]
     static assert(isStaticArray!(typeof(tt)));
 }

 void main(string[] args)
 {
     foo(1,2,3);
 }
 =====

 Note that you should check that the elements of T[] have all 
 the same type.
 It shouldn't be a big problem.
Thanks, that way of doing it does work. I guess that means there's no easy way to make sure all T are the same type without a template constraint? It's not that hard, you're right, but it's less elegant I think. Just a shame that auto makeStruct(long[] nums...)(); doesn't work; it seems like it would have. Oh well, we can't have everything. Thanks for the help though!
Jul 14 2016
parent reply Basile B. <b2.temp gmx.com> writes:
On Friday, 15 July 2016 at 04:31:08 UTC, Devin Hill wrote:
 Thanks, that way of doing it does work. I guess that means 
 there's no easy way to make sure all T are the same type 
 without a template constraint?
Yes, immediatly, now, I think that a contraint has to be used. But you have several choices for the constraint, two obvious: - recursive template. - staticIota in a foreach. (aliasSeqOf!(iota(1, T.length))
Jul 14 2016
parent reply Basile B. <b2.temp gmx.com> writes:
On Friday, 15 July 2016 at 04:38:03 UTC, Basile B. wrote:
 two obvious:

 - recursive template.
 - staticIota in a foreach. (aliasSeqOf!(iota(1, T.length))
even better: template sameType(T...) { import std.meta; static if (!T.length) enum sameType = false; else enum sameType = NoDuplicates!T.length == 1; }
Jul 14 2016
parent reply Devin Hill <98devin gmail.com> writes:
On Friday, 15 July 2016 at 05:23:15 UTC, Basile B. wrote:
 even better:

 template sameType(T...)
 {
     import std.meta;
     static if (!T.length)
         enum sameType = false;
     else
         enum sameType = NoDuplicates!T.length == 1;
 }
Yeah, that's basically what I ended up doing, but since I also needed to constrain the type of T, I added is(T[0] : long) to the condition. It works pretty well! Granted, it doesn't allow for calling it in two ways like a variadic version would have: foo(1, 2, 3) // works with this setup foo([1, 2, 3]) // doesn't, but would only be occasionally useful anyway but all in all it's a decent workaround for the problem.
Jul 15 2016
parent Mike Parker <aldacron gmail.com> writes:
On Friday, 15 July 2016 at 15:04:22 UTC, Devin Hill wrote:

 to the condition. It works pretty well! Granted, it doesn't 
 allow for calling it in two ways like a variadic version would 
 have:

 foo(1, 2, 3)   // works with this setup
 foo([1, 2, 3]) // doesn't, but would only be occasionally 
 useful anyway

 but all in all it's a decent workaround for the problem.
It isn't too much effort to add support for both: ``` import std.traits : isArray, ForeachType; import std.stdio : writeln; void func(Args...)(Args args) if(is(Args[0] : long) || (isArray!(Args[0]) && is(ForeachType!(Args[0]) : long))) { static if(isArray!(Args[0])) { foreach(i; args[0]) writeln(i); } else { foreach(arg; args) writeln(arg); } } void main() { func(10, 20, 30, 40); func([1, 2, 3, 4, 5]); } ``` Or, alternatively, to support multiple arrays: ``` void func(Args...)(Args args) if(is(Args[0] : long) || (isArray!(Args[0]) && is(ForeachType!(Args[0]) : long))) { foreach(arg; args) { static if(isArray!(Args[0])) { foreach(i; arg) writeln(i); } else writeln(arg); } } void main() { func(10, 20, 30, 40); func([1, 2, 3, 4, 5], [100, 200, 300, 400]); } ```
Jul 15 2016