www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - get from tuple by type

reply "Charles Cooper" <chevil gmail.com> writes:
C++14 has:
template<class T, class... Types> constexpr T& 
get(tuple<Types...>& t);
Which allows you to get a member of the tuple struct by type. Is 
there an idiomatic / library way to do this in D? Preferably by 
indexing.

Here is what I have, it is ugly but works:
/* CODE */
static import std.stdio;
static import std.typecons;
template GetByType(alias tuple_instance, member_t)
{
     ref member_t GetByType() nothrow  nogc  safe {
         alias tuple_t = typeof(tuple_instance);
         static assert(std.typecons.isTuple!tuple_t);
         enum long idx = std.typetuple.staticIndexOf!(member_t, 
tuple_instance.Types);
         static if(-1 != idx)
             return tuple_instance[idx];
         else static assert(false); //better error message
     }
}

static assert(2.5 == GetByType!(std.typecons.tuple(1,2.5), 
double));
static assert(2.5 == GetByType!(std.typecons.tuple(1,2.5,3.1), 
double));
void main() {
     auto foo = std.typecons.tuple(1,2.5);
     std.stdio.writeln(GetByType!(foo, double));
}
/* CODE */

Is there a better way to do this?
Mar 15 2015
next sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 15 Mar 2015 21:59:16 +0000, Charles Cooper wrote:

 C++14 has:
 template<class T, class... Types> constexpr T& get(tuple<Types...>& t);
 Which allows you to get a member of the tuple struct by type. Is there
 an idiomatic / library way to do this in D? Preferably by indexing.
why indexing? functional programming is fun! import std.typecons : tuple; template GetByType(Type, Tuple...) { import std.typecons : isTuple; static if (Tuple.length =3D=3D 0) static assert(false, Type.stringof~" not found"); else static if (Tuple.length =3D=3D 1 && isTuple!(typeof(Tuple[0]))) enum GetByType =3D GetByType!(Type, Tuple[0].expand); else static if (is(typeof(Tuple[0]) =3D=3D Type)) enum GetByType =3D Tuple[0]; else enum GetByType =3D GetByType!(Type, Tuple[1..$]); } static assert(2.5 =3D=3D GetByType!(double, 1, 2.5)); static assert(2.5 =3D=3D GetByType!(double, 1, 2.5, 3.1)); //static assert(2.5 =3D=3D GetByType!(char, 1, 2.5, 3.1)); static assert(2.5 =3D=3D GetByType!(double, std.typecons.tuple(1, 2.5))); static assert(2.5 =3D=3D GetByType!(double, std.typecons.tuple(1,2.5,3.1)= )); =
Mar 15 2015
parent reply "weaselcat" <weaselcat gmail.com> writes:
On Sunday, 15 March 2015 at 22:21:04 UTC, ketmar wrote:
 On Sun, 15 Mar 2015 21:59:16 +0000, Charles Cooper wrote:

 C++14 has:
 template<class T, class... Types> constexpr T& 
 get(tuple<Types...>& t);
 Which allows you to get a member of the tuple struct by type. 
 Is there
 an idiomatic / library way to do this in D? Preferably by 
 indexing.
why indexing? functional programming is fun! import std.typecons : tuple; template GetByType(Type, Tuple...) { import std.typecons : isTuple; static if (Tuple.length == 0) static assert(false, Type.stringof~" not found"); else static if (Tuple.length == 1 && isTuple!(typeof(Tuple[0]))) enum GetByType = GetByType!(Type, Tuple[0].expand); else static if (is(typeof(Tuple[0]) == Type)) enum GetByType = Tuple[0]; else enum GetByType = GetByType!(Type, Tuple[1..$]); } static assert(2.5 == GetByType!(double, 1, 2.5)); static assert(2.5 == GetByType!(double, 1, 2.5, 3.1)); //static assert(2.5 == GetByType!(char, 1, 2.5, 3.1)); static assert(2.5 == GetByType!(double, std.typecons.tuple(1, 2.5))); static assert(2.5 == GetByType!(double, std.typecons.tuple(1,2.5,3.1)));
Seems like a useful feature(useful enough to get past the C++ standards committee,) consider submitting a phobos PR?
Mar 15 2015
next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 15 Mar 2015 22:35:03 +0000, weaselcat wrote:

 Seems like a useful feature(useful enough to get past the C++
 standards committee,) consider submitting a phobos PR?
ah, c++ committee accepts by randomness. ;-) honestly, i can't see why=20 it's useful and where it can be used. and it's so easy to write anyway,=20 so i think that there is virtually no sense in adding something like this=20 to Phobos.=
Mar 15 2015
prev sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
p.s. to be clear: it's freaking hard to do metaprogramming and template=20
functional programming in c++, that's why c++ committee accepts such=20
things. and it's very easy to write such code in D, so this is a good=20
excersise for newcomers and almost no-brainer for expirienced D user.=
Mar 15 2015
parent reply "Charles Cooper" <chevil gmail.com> writes:
Sure. It is also easy to write merge sort. Or 
std.typetuple.Erase. Or Tuple.opIndex(size_t). But that doesn't 
mean everybody does it. Some utilities (and I am not saying this 
is, but it could be) are widely used enough that it makes sense 
to put them in the standard.

On Sunday, 15 March 2015 at 22:44:21 UTC, ketmar wrote:
 p.s. to be clear: it's freaking hard to do metaprogramming and 
 template
 functional programming in c++, that's why c++ committee accepts 
 such
 things. and it's very easy to write such code in D, so this is 
 a good
 excersise for newcomers and almost no-brainer for expirienced D 
 user.
Mar 15 2015
parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 15 Mar 2015 23:17:34 +0000, Charles Cooper wrote:

 Sure. It is also easy to write merge sort. Or std.typetuple.Erase. Or
 Tuple.opIndex(size_t). But that doesn't mean everybody does it. Some
 utilities (and I am not saying this is, but it could be) are widely used
 enough that it makes sense to put them in the standard.
sorry if you feel offended, i never meant that. what i meant is that it's=20 hard in c++, but easy in D when one knows how to do that. i've learned D=20 mostly by reading other people code, and there is nothing wrong in asking=20 questions, quite the contrary. but the requested solution is not "universal" enough to be included in=20 Phobos (what if i want an index instead of a value? or (index, value)=20 tuple? or just check if it is there? or find either `int` or `double`?).=20 it's easier to write specialized template for required cases than to try=20 to make it generic (and complex) enough. but it's much harder to write=20 that in c++, to the extent that it's easier to include that things in=20 standard. just stay with us and you will see that D shines in such things (and in=20 many other areas too ;-).=
Mar 15 2015
parent reply "Charles Cooper" <chevil gmail.com> writes:
Not offended at all :), in fact it was not even my suggestion 
that it be included in the standard. I was just knee jerk 
reacting to the comment that, just because something is simple to 
do precludes it from getting standardized

On Sunday, 15 March 2015 at 23:28:18 UTC, ketmar wrote:
 sorry if you feel offended, i never meant that. what i meant is 
 that it's
 hard in c++, but easy in D when one knows how to do that. i've 
 learned D
 mostly by reading other people code, and there is nothing wrong 
 in asking
 questions, quite the contrary.

 but the requested solution is not "universal" enough to be 
 included in
 Phobos (what if i want an index instead of a value? or (index, 
 value)
 tuple? or just check if it is there? or find either `int` or 
 `double`?).
 it's easier to write specialized template for required cases 
 than to try
 to make it generic (and complex) enough. but it's much harder 
 to write
 that in c++, to the extent that it's easier to include that 
 things in
 standard.

 just stay with us and you will see that D shines in such things 
 (and in
 many other areas too ;-).
Mar 15 2015
parent ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 15 Mar 2015 23:30:48 +0000, Charles Cooper wrote:

 Not offended at all :), in fact it was not even my suggestion that it be
 included in the standard. I was just knee jerk reacting to the comment
 that, just because something is simple to do precludes it from getting
 standardized
ah, i see. but this has disadvantages too: it's hard to remember=20 everything the std has, and people will write simple things again and=20 again just 'cause it is faster than trying to find the corresponding=20 function in standard library. search is not of great help here too=20 (unless search engine can read programmer's mind and understand what he=20 really wants ;-). and i still can't see when this can be handy. i'm almost sure that there=20 is a better way to do the thing programmer wants to achieve with such=20 code.=
Mar 15 2015
prev sibling next sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Charles Cooper:

 Is there a better way to do this?
Can you show some use cases for this, and isn't "foo[1]" better? Bye, bearophile
Mar 15 2015
parent reply "Charles Cooper" <chevil gmail.com> writes:
foo[1] is sometimes better, but not always. One has to go back to 
the definition of the thing and literally calculate by hand which 
element of the tuple you want, and then try compiling it, and so 
forth. Although the type system will guarantee that you 
eventually get it right it is a waste of time.

void external_api1_react_to_event(meters_t, time_t);
void external_api2_react_to_event(time_t, meters_t);
alias event_t = Tuple!(time_t, meters_t)
// .. some time later..
external_api1_react_to_event(event_t.get(meters_t), 
event_t.get(time_t));

Yes, I could say
external_api1_react_to_event(event_t[1], event_t[0])
.. but that is barbaric. It's a productivity sink because I have 
to go back to the original definition, align the arguments, and 
then context switch back to whatever I was working on before.

And yes, I could use names. But then you are subject to name 
clashes and using strings instead of types as member identifiers 
is more prone to error anyways. Ever gotten this wrong before --
void CRITICAL_TO_GET_THIS_RIGHT(uint cents, uint dollars);
....
alias params_t = Tuple!(uint, "dollars", uint, "cents");
....
params_t params;
params.dollars = 0;
params.cents = 99;
CRITICAL_TO_GET_THIS_RIGHT(params.expand);
// compilation succeeds, bank fails.

In conclusion: this is an important feature because it allows you 
to enforce type safety when working with tuples (e.g. in function 
parameters) efficiently, without taking too much of the 
programmer's time.

On Sunday, 15 March 2015 at 22:32:48 UTC, bearophile wrote:
 Charles Cooper:

 Is there a better way to do this?
Can you show some use cases for this, and isn't "foo[1]" better? Bye, bearophile
Mar 15 2015
next sibling parent reply "anonymous" <anonymous example.com> writes:
On Sunday, 15 March 2015 at 23:13:58 UTC, Charles Cooper wrote:
 And yes, I could use names. But then you are subject to name 
 clashes and using strings instead of types as member 
 identifiers is more prone to error anyways. Ever gotten this 
 wrong before --
 void CRITICAL_TO_GET_THIS_RIGHT(uint cents, uint dollars);
 ....
 alias params_t = Tuple!(uint, "dollars", uint, "cents");
 ....
 params_t params;
 params.dollars = 0;
 params.cents = 99;
 CRITICAL_TO_GET_THIS_RIGHT(params.expand);
 // compilation succeeds, bank fails.
How would GetByType help here? Both members are uint, so you can't distinguish them by type. And if you gave them distinct types, the bad example here wouldn't compile anymore either.
Mar 15 2015
parent "Charles Cooper" <chevil gmail.com> writes:
True. If I had to do something involving such an API I would 
first wrap the API with a type safe one before doing anything 
else.

void external_api_do_something(uint dollars, uint cents);
/* I think this could somehow be automated with staticMap and 
ParameterTypeTuple / ParameterIdentifierTuple */
alias dollars_t = Typedef!(uint, uint.init, "dollars");
alias cents_t = Typedef!(uint, uint.init, "cents");

void internal_api_do_something(dollars_t, cents_t);

On Sunday, 15 March 2015 at 23:20:22 UTC, anonymous wrote:
 On Sunday, 15 March 2015 at 23:13:58 UTC, Charles Cooper wrote:

 How would GetByType help here? Both members are uint, so you 
 can't distinguish them by type. And if you gave them distinct 
 types, the bad example here wouldn't compile anymore either.
Mar 15 2015
prev sibling parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Charles Cooper:

 Yes, I could say
 external_api1_react_to_event(event_t[1], event_t[0])
 .. but that is barbaric. It's a productivity sink because I 
 have to go back to the original definition, align the 
 arguments, and then context switch back to whatever I was 
 working on before.
If you are experiencing those problems it's probably the way D/Phobos to tell you to not use basic tuples for your purpose. Use tuples with named fields (or even structs). Take also a look at Algebraic in std.variant. Bye, bearophile
Mar 15 2015
parent "Charles Cooper" <chevil gmail.com> writes:

Thanks! This is fascinating, really a breath of fresh air coming 
from the C++ way of doing things.

On Sunday, 15 March 2015 at 23:31:59 UTC, bearophile wrote:
 If you are experiencing those problems it's probably the way 
 D/Phobos to tell you to not use basic tuples for your purpose. 
 Use tuples with named fields (or even structs).
 Take also a look at Algebraic in std.variant.

 Bye,
 bearophile
Mar 15 2015
prev sibling parent reply "anonymous" <anonymous example.com> writes:
On Sunday, 15 March 2015 at 21:59:18 UTC, Charles Cooper wrote:
 C++14 has:
 template<class T, class... Types> constexpr T& 
 get(tuple<Types...>& t);
 Which allows you to get a member of the tuple struct by type. 
 Is there an idiomatic / library way to do this in D? Preferably 
 by indexing.
I don't think there is. I don't know if there should be. Distinguishing tuple fields by their type doesn't seem very useful to me, since multiple fields can have the same type.
 Here is what I have, it is ugly but works:
 /* CODE */
 static import std.stdio;
 static import std.typecons;
 template GetByType(alias tuple_instance, member_t)
 {
     ref member_t GetByType() nothrow  nogc  safe {
         alias tuple_t = typeof(tuple_instance);
         static assert(std.typecons.isTuple!tuple_t);
         enum long idx = std.typetuple.staticIndexOf!(member_t, 
 tuple_instance.Types);
         static if(-1 != idx)
             return tuple_instance[idx];
         else static assert(false); //better error message
     }
 }

 static assert(2.5 == GetByType!(std.typecons.tuple(1,2.5), 
 double));
 static assert(2.5 == GetByType!(std.typecons.tuple(1,2.5,3.1), 
 double));
 void main() {
     auto foo = std.typecons.tuple(1,2.5);
     std.stdio.writeln(GetByType!(foo, double));
 }
 /* CODE */

 Is there a better way to do this?
I went over it (some notes below): ---- import std.typecons: Tuple; auto ref inout(T) getFirst(T, Types ...)(auto ref inout(Tuple!Types) t) { import std.typetuple: staticIndexOf; enum idx = staticIndexOf!(T, Types); static if(-1 != idx) return t[idx]; else static assert(false); //better error message } unittest { import std.typecons: tuple; assert(2.5 == tuple(1, 2.5).getFirst!double); assert(2.5 == tuple(1, 2.5, 3.1).getFirst!double); static assert(2.5 == tuple(1, 2.5).getFirst!double); // CTFE static assert(!__traits(compiles, tuple(1, 2.5).getFirst!string)); // lvalue tuple => lvalue result auto m = tuple(1, 2.5); m.getFirst!double = 2.1; assert(m[1] == 2.1); // rvalue tuple => rvalue result static assert(!__traits(compiles, &tuple(1, 2.5).getFirst!double)); // immutable/const immutable i = tuple(1, 2.5); assert(2.5 == i.getFirst!double); const c = tuple(1, 2.5); assert(2.5 == c.getFirst!double); } void main() { import std.stdio: writeln; import std.typecons: tuple; auto foo = tuple(1, 2.5); writeln(foo.getFirst!double); } ---- Using combined syntax for function template. Made the tuple a function parameter like in the C++ version. I don't see the point in having it a template alias parameter. Dropped `nothrow nogc safe`, since copying the member might not be any of that. They are inferred when possible. Employing inout and `auto ref`. More tests. unittest block instead of `static assert`s. Bikeshedding: Changed name to "getFirst", since subsequent values of the same type are ignored. Named things more like the C++ version: member_t -> T, tuple_instance -> t. Use selective imports instead of static imports. Use more common casing: types and type templates are PascalCased, everything else is camelCased. Brace placement.
Mar 15 2015
parent "Charles Cooper" <chevil gmail.com> writes:
Thanks for the style recommendations.

On Sunday, 15 March 2015 at 23:14:32 UTC, anonymous wrote:
 I don't think there is. I don't know if there should be. 
 Distinguishing tuple fields by their type doesn't seem very 
 useful to me, since multiple fields can have the same type.

 Using combined syntax for function template.

 Made the tuple a function parameter like in the C++ version. I 
 don't see the point in having it a template alias parameter.

 Dropped `nothrow  nogc  safe`, since copying the member might 
 not be any of that. They are inferred when possible.

 Employing inout and `auto ref`.

 More tests. unittest block instead of `static assert`s.

 Bikeshedding:

 Changed name to "getFirst", since subsequent values of the same 
 type are ignored.

 Named things more like the C++ version: member_t -> T, 
 tuple_instance -> t.

 Use selective imports instead of static imports.

 Use more common casing: types and type templates are 
 PascalCased, everything else is camelCased.

 Brace placement.
Mar 15 2015