Thread overview
My template tuple code does not compile
Feb 26, 2019
Victor Porton
Feb 26, 2019
Q. Schroll
Feb 26, 2019
Victor Porton
Feb 26, 2019
H. S. Teoh
Feb 26, 2019
Q. Schroll
Feb 27, 2019
Victor Porton
Feb 27, 2019
Simen Kjærås
Feb 27, 2019
Q. Schroll
Feb 27, 2019
Victor Porton
Mar 01, 2019
Simen Kjærås
February 26, 2019
Compilation of unittest at the bottom of this file fails with an error. What is my error?

https://github.com/vporton/struct-params-dlang/blob/c32cfde60dbb03cb80a4a8aeb8185f5c86705790/source/struct_params.d

It is very important both for this useful little D project and my bigger research project. Please help.

source/struct_params.d(74,30): Error: found `,` when expecting `.` following int
source/struct_params.d(74,32): Error: found `"x"` when expecting identifier following `int`.
source/struct_params.d(74,44): Error: found `,` when expecting `.` following float
source/struct_params.d(74,46): Error: found `"y"` when expecting identifier following `float`.
February 26, 2019
On Tuesday, 26 February 2019 at 21:43:31 UTC, Victor Porton wrote:
> Compilation of unittest at the bottom of this file fails with an error. What is my error?

I cannot tell you, why exactly you get these error messages. I can explain you the probable cause of the errors. I have not tested anything of what I tell you, as it is very vague.

You have the line

   ProviderParams("S", ((int, "x"), (float, "y")));

Why do you even expect it to compile?
First, ProviderParams is a mixin template, so you'd need to instantiate it with the ! operator. Then, the expressions (int, "x") and so on do not make sense in D. You could have compile-time tuples (using AliasSeq from std.meta) containing types and other compile-time known stuff, but it wouldn't help directly.
You'd use the mixin template like this:

   mixin ProviderParams!("S", int, "x", float, "y");
   ^-- necessary       ^-- necessary

Grouping arguments could be done, but from my experience, it does not buy you anything; rather it makes it worse. Better just deal with heterogeneous stuff just like std.typecons.Tuple does.

You should definitely comment your code. Explain what you intend to do. It helps people helping you.
February 26, 2019
On Tuesday, 26 February 2019 at 22:51:15 UTC, Q. Schroll wrote:
> On Tuesday, 26 February 2019 at 21:43:31 UTC, Victor Porton wrote:
>> Compilation of unittest at the bottom of this file fails with an error. What is my error?
...
> You have the line
>
>    ProviderParams("S", ((int, "x"), (float, "y")));
>
> Why do you even expect it to compile?
> First, ProviderParams is a mixin template, so you'd need to instantiate it with the ! operator. Then, the expressions (int, "x") and so on do not make sense in D. You could have compile-time tuples (using AliasSeq from std.meta) containing types and other compile-time known stuff, but it wouldn't help directly.
> You'd use the mixin template like this:
>
>    mixin ProviderParams!("S", int, "x", float, "y");
>    ^-- necessary       ^-- necessary
>
> Grouping arguments could be done, but from my experience, it does not buy you anything; rather it makes it worse. Better just deal with heterogeneous stuff just like std.typecons.Tuple does.
>
> You should definitely comment your code. Explain what you intend to do. It helps people helping you.

After fixing the error you pointed me, it does not work too:

mixin ProviderParams!("S", ((int, "x"), (float, "y")));

Also: Can I nevertheless group arguments?
February 26, 2019
On Tue, Feb 26, 2019 at 10:56:37PM +0000, Victor Porton via Digitalmars-d-learn wrote: [...]
> After fixing the error you pointed me, it does not work too:
> 
> mixin ProviderParams!("S", ((int, "x"), (float, "y")));

Try this:

	mixin ProviderParams!("S", int, "x", float, "y");


> Also: Can I nevertheless group arguments?

Template argument lists auto-expand, so even if you could group arguments, they make no functional difference.  If you truly need to distinguish between groups of arguments, you have to pack them into a non-autoexpanding group, for example:

	template Group(Args...) {
		// Note: NOT eponymous.
		alias contents = Args;
	}

	mixin ProviderParams!("S", Group!(int, "x"), Group!(float, "y"));

Keep in mind, however, that inside ProviderParams you will need to explicitly access .contents. For example:

	mixin template ProviderParams(Args...) {
		static foreach (arg; Args[1 .. $]) {{
			alias type = arg.contents[0];
			alias name = arg.contents[1];
		}}
	}


T

-- 
Prosperity breeds contempt, and poverty breeds consent. -- Suck.com
February 26, 2019
On Tuesday, 26 February 2019 at 22:56:37 UTC, Victor Porton wrote:
> On Tuesday, 26 February 2019 at 22:51:15 UTC, Q. Schroll wrote:
>> Grouping arguments could be done, but from my experience, it does not buy you anything; rather it makes it worse. Better just deal with heterogeneous stuff just like std.typecons.Tuple does.
>
> After fixing the error you pointed me, it does not work too:
>
> mixin ProviderParams!("S", ((int, "x"), (float, "y")));
>
> Also: Can I nevertheless group arguments?

No, not the way you do. The module std.meta defines AliasSeq as follows:

    alias AliasSeq(X...) = X;

It is literally equivalent to

    template AliasSeq(X...)
    {
        alias AliasSeq = X; // "eponymous template member"
    }

In

    mixin ProviderParams!("S", ((int, "x"), (float, "y")));

the parenthesized stuff like (int, "x") is invalid in terms of formal grammar. You could use AliasSeq!(int, "x") if you really want to display grouping in your source code. Note however that this does not do anything. The sequence is being flattened by the compiler, i.e.

    mixin ProviderParams!("S", AliasSeq!(AliasSeq!(int, "x"), AliasSeq!(float, "y")));

is exactly the same as

    mixin ProviderParams!("S", int, "x", float, "y");

which I wrote in my answer.

When you don't use a eponymous template member, you can access them manually:

    template Pack(X...)
    {
        alias Contents = X; // not "eponymous template member"
    }

Using this, you can create what I call packs. For some template T, while T!(AliasSeq!(int, bool), AliasSeq!(float, string, long)) is the same as writing T!(int, bool), the T!(Pack!(int, bool), Pack!(float, string, long)) is different from T!(int, bool, float, string, long). In the AliasSeq template, the eponymous template member feature of D immediately expands the sequence.

If the template T is defined like this:

    template T(Args...)
    { .. }

In the AliasSeq case, Args[0] is int, Args[1] is bool, Args[2] is float, ...
In the Pack case, Args[0] is Pack!(int, bool), Args[1] is Pack!(float, string, long), and Args[0].Contents[0] is int, Args[0].Contents[1] is bool, Args[1].Contents[0] is float ...

I've used Packs once to get the Cartesian product of an AliasSeq of Packs. It is a mess. I'd only use Packs when absolutely necessary; in your case, it does not seem so.

In your case, you seem to need some initial thing "S" and then pairs of things.

    template you_name_it(Arg, args...)
    if (args.length % 2 == 0) // ensures pairs
    {
        static foreach (i; args.length / 2)
        {
            // access the type by args[2*i]
            // access the name (or whatever) by args[2*i+1]
        }
    }

You could get the sequence of types and the sequence of names using

    alias Types = Stride!(2, args);
    alias Names = Stride!(2, args[1 .. $]);
February 27, 2019
After following your suggestion to rewrite it with Stride it does not work either. I assume the error is somehow related to allSatisfy!.

https://github.com/vporton/struct-params-dlang/blob/c1adc86672f46fd6b743903cc270dceef120a8fe/source/struct_params.d

Please help. It is important for both D community and world at large.
February 27, 2019
On Wednesday, 27 February 2019 at 03:53:35 UTC, Victor Porton wrote:
> After following your suggestion to rewrite it with Stride it does not work either. I assume the error is somehow related to allSatisfy!.
>
> https://github.com/vporton/struct-params-dlang/blob/c1adc86672f46fd6b743903cc270dceef120a8fe/source/struct_params.d
>
> Please help. It is important for both D community and world at large.

Your problem is exactly here:

    allSatisfy!(x => is(typeof(x) == string), Names)

allSatisfy expects its first parameter to be a template, not a function. We can introduce a simple helper template:

template isA(T) {
    enum isA(alias U) = is(typeof(T) == U);
}

Then simply replace the code above with allSatisfy!(isA!string, Names).

--
  Simen
February 27, 2019
On Wednesday, 27 February 2019 at 03:53:35 UTC, Victor Porton wrote:
> After following your suggestion to rewrite it with Stride it does not work either. I assume the error is somehow related to allSatisfy!.
>
> https://github.com/vporton/struct-params-dlang/blob/c1adc86672f46fd6b743903cc270dceef120a8fe/source/struct_params.d
>
> Please help. It is important for both D community and world at large.

In

    static assert(allSatisfy!(x => isType!x, Types) && ...

you use `allSatisfy` as if it took a lambda function or something like that. It does not. It takes a expects a template.

First things first: The spec¹ says that allSatisfy!(F, T) «tests whether all given items satisfy a template predicate, i.e. evaluates to F!(T[0]) && F!(T[1]) && ... && F!(T[$ - 1]).» Here, `F` is not a lambda, it's a template.

In your case, you can use `isTypeTuple!Types` from the library² to check what you intend to check. For the values, unfortunately there is no library function to check that they are all strings. A specific solution is to use a (static) template

    enum bool isStringValue(alias x) = is(typeof(x) == string);

and feed it to `allSatisfy`:

    allSatisfy!(isStringValue, Names)

¹ https://dlang.org/library/std/meta/all_satisfy.html
² https://dlang.org/phobos/std_traits.html#isTypeTuple


February 27, 2019
I rewrote it again:

https://github.com/vporton/struct-params-dlang/blob/f50f7e5919f90b1d06bf0cc08e3055548aad1797/source/struct_params.d

But it does not work :-( What is my error?

source/struct_params.d(43,60): Error: function expected before `()`, not `()` of type `()`
source/struct_params.d(44,43): Error: undefined identifier `fieldsWithDefaults`
source/struct_params.d(56,11): Error: template instance `struct_params.ProviderParamsCode!("S", int, "x", float, "y")` error instantiating
source/struct_params.d(82,5): Error: mixin `struct_params.__unittest_L81_C1.ProviderParams!("S", int, "x", float, "y")` error instantiating
March 01, 2019
On Wednesday, 27 February 2019 at 22:45:03 UTC, Victor Porton wrote:
> I rewrote it again:
>
> https://github.com/vporton/struct-params-dlang/blob/f50f7e5919f90b1d06bf0cc08e3055548aad1797/source/struct_params.d
>
> But it does not work :-( What is my error?
>
> source/struct_params.d(43,60): Error: function expected before `()`, not `()` of type `()`
> source/struct_params.d(44,43): Error: undefined identifier `fieldsWithDefaults`
> source/struct_params.d(56,11): Error: template instance `struct_params.ProviderParamsCode!("S", int, "x", float, "y")` error instantiating
> source/struct_params.d(82,5): Error: mixin `struct_params.__unittest_L81_C1.ProviderParams!("S", int, "x", float, "y")` error instantiating

You seem to have misunderstood how staticMap works. It operates on a compile-time list of values, and only takes a single set of parameters: staticMap!(fn, item0, item1, item...), not staticMap!fn(item0, item1, item...).

In addition, Types.length.iota will create a run-time range of values, not a compile-time AliasSeq of values. For this, you can use std.meta.aliasSeqOf: staticMap!(regularField, aliasSeqOf!(Types.length.iota)).

Next, join expects a range, not an AliasSeq. In other words, staticMap!(...).join('\n') will fail to compile - staticMap!(...) will need to be turned into a range of some sort. We can do this the same way we'd turn any list of values into a range - by putting brackets around it. Just like [1,2,3] is a valid array, so is [staticMap!(...)], supposing the elements have some common type.

But wait - there's more! You're using __traits(identifier, Types[i]). As pointed out in https://forum.dlang.org/post/smgsgycpgvtagfsdxapi@forum.dlang.org, this will fail for built-in types, as they don't have an identifier. You can get their names, as well as the name of any other type with Types[i].stringof.

Lastly, you have not imported std.typecons, so your use of Nullable will fail.

So, that's the issues that cause it to not compile. There are issues also in the code that isn't instantiated, i.e. callFunctionWithParamsStruct and callMemberFunctionWithParamsStruct. Since these aren't used, they don't cause compile failure yet. The main issue here is exactly the same as above: a conflation of run-time and compile-time responsibilities, this time when using map.

Since map is a run-time function, it can't do compile-time things like __traits(getMember) - you'll need to use staticMap for that. However, there's a much easier solution:

auto callFunctionWithParamsStruct(alias f, S)(S s) {
    return f(s.tupleof);
}

The same thing can be done for callMemberFunctionWithParamsStruct.

Now, I will note that no checking is done that parameter names and field names match, so void fun(int numberOfBeans, string nameOfCat) can easily be called with struct S { int temperatureOfPudding; string declarationOfIndependence; }, but I will assume that's because you haven't gotten around to it yet.

--
  Simen