Thread overview
Are templates with variadic value parameters possible?
Jul 15, 2016
Devin Hill
Jul 15, 2016
Basile B.
Jul 15, 2016
Devin Hill
Jul 15, 2016
Basile B.
Jul 15, 2016
Basile B.
Jul 15, 2016
Devin Hill
Jul 15, 2016
Mike Parker
July 15, 2016
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.
July 15, 2016
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.
July 15, 2016
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!
July 15, 2016
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))


July 15, 2016
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;
}
July 15, 2016
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.
July 15, 2016
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]);
}
```