Jump to page: 1 2
Thread overview
Indexing with an arbitrary type
Aug 01, 2016
Alex
Aug 01, 2016
Jonathan M Davis
Aug 01, 2016
Alex
Aug 01, 2016
Jonathan M Davis
Aug 01, 2016
Alex
Aug 01, 2016
Jonathan M Davis
Aug 01, 2016
Alex
Aug 03, 2016
Alex
Aug 03, 2016
Jonathan M Davis
Aug 03, 2016
Alex
Aug 03, 2016
Alex
Aug 03, 2016
Jonathan M Davis
Aug 03, 2016
Alex
Aug 04, 2016
Alex
Aug 05, 2016
Jonathan M Davis
Aug 06, 2016
Alex
August 01, 2016
Hi everybody,
I have a question and a half on templates and ranges, this time.
Say, I have two functions:

auto f1(T, S)(T t, S s) if(isIntegral!T && isRandomAccessRange!S)
{
    return s[t];
}

auto f2(T, S)(T t, RandomAccessFinite!S raf) if(isIntegral!T)
{
    return raf[t];
}

and a
class myClass : RandomAccessFinite!ubyte {...}
which implements all the methods needed by the RandomAccessFinite interface.

then, I could use this in my main by just

void main()
{
    myClass mC = new myClass();
    writeln(f2(1, mC));
    writeln(f1(1, mC));

    ubyte[] arr = [0, 42, 2, 3, 4];
    writeln(f1(1, arr));
    //writeln(f2(1, arr)); //this fails and is the first part of the question.
}

so, the first question is, why the method using the RandomAccessFinite interface fails on using an array? Did I miss a method, which is not supported by an array?

But the main question is about the other part, about the constraint to the first parameter to my functions.
It seems strange to me to use "isIntegral" here, as this is some kind of unrelated for something used as an index.
Is there anything more appropriate to check? What kind of interface/trait has an index to fulfill?
August 01, 2016
On Monday, August 01, 2016 11:06:54 Alex via Digitalmars-d-learn wrote:
> Hi everybody,
> I have a question and a half on templates and ranges, this time.
> Say, I have two functions:
>
> auto f1(T, S)(T t, S s) if(isIntegral!T && isRandomAccessRange!S)
> {
>      return s[t];
> }
>
> auto f2(T, S)(T t, RandomAccessFinite!S raf) if(isIntegral!T)
> {
>      return raf[t];
> }
>
> and a
> class myClass : RandomAccessFinite!ubyte {...}
> which implements all the methods needed by the RandomAccessFinite
> interface.
>
> then, I could use this in my main by just
>
> void main()
> {
>      myClass mC = new myClass();
>      writeln(f2(1, mC));
>      writeln(f1(1, mC));
>
>      ubyte[] arr = [0, 42, 2, 3, 4];
>      writeln(f1(1, arr));
>      //writeln(f2(1, arr)); //this fails and is the first part of
> the question.
> }
>
> so, the first question is, why the method using the RandomAccessFinite interface fails on using an array? Did I miss a method, which is not supported by an array?

An array does not implement RandomAccessFinite, which is an interface that you created. So, a function that takes a RandomAccessFinite is not going to accept an array. A dynamic array will match isRandomAccessRange, but that has nothing to do with interfaces.

> But the main question is about the other part, about the
> constraint to the first parameter to my functions.
> It seems strange to me to use "isIntegral" here, as this is some
> kind of unrelated for something used as an index.
> Is there anything more appropriate to check? What kind of
> interface/trait has an index to fulfill?

What's strange? If you want to accept any integer type, then isIntegral!T would do it. A _better_ thing to do would be to make it so that it's just size_t and not templatize the type, since indices really should be size_t normally (and if the system is 32-bit, then isIntegral!T will accept long and ulong, whereas size_t is uint, and if you pass a long, you'll get a compilation error for arrays, since they take size_t for indexing; it won't matter for 64-bit though, since size_t is ulong there). So,

auto f1(R)(size_t index, R range)
    if(isRandomAccessRange!R)
{
    return range[index];
}

would be better, but aside from the 32-bit issues, isIntegral will work.

- Jonathan M Davis

August 01, 2016
On Monday, 1 August 2016 at 13:52:56 UTC, Jonathan M Davis wrote:
> An array does not implement RandomAccessFinite, which is an interface that you created. So, a function that takes a RandomAccessFinite is not going to accept an array. A dynamic array will match isRandomAccessRange, but that has nothing to do with interfaces.

It's ok for me to say, that some types do not implement an interface to show some abilities, even if the interface, which has to be implemented to show the same abilities is given/known/public/exposed... etc...
So, I think this part is ok now, I think...

>> But the main question is about the other part, about the
>> constraint to the first parameter to my functions.
>> It seems strange to me to use "isIntegral" here, as this is some
>> kind of unrelated for something used as an index.
>> Is there anything more appropriate to check? What kind of
>> interface/trait has an index to fulfill?
>
> What's strange? If you want to accept any integer type, then isIntegral!T would do it. A _better_ thing to do would be to make it so that it's just size_t and not templatize the type, since indices really should be size_t normally (and if the system is 32-bit, then isIntegral!T will accept long and ulong, whereas size_t is uint, and if you pass a long, you'll get a compilation error for arrays, since they take size_t for indexing; it won't matter for 64-bit though, since size_t is ulong there). So,
>
> auto f1(R)(size_t index, R range)
>     if(isRandomAccessRange!R)
> {
>     return range[index];
> }
>
> would be better, but aside from the 32-bit issues, isIntegral will work.
>
> - Jonathan M Davis

This goes in a different direction I want to. I don't have anything against simplification to size_t, indeed I have it in this way currently. But what I want is something like the following:

having
    alias MyIndex = int
and
    MyIndex s = MyInd(1);
    writeln(f1(s, arr)); //gives 42, as expected

Now, I want to define a
struct S
{
    int index;
    ??? what else ??? // alias index this; doesn't help
}
and still being able to have
    alias MyIndex = S
and nothing else should be changed.


August 01, 2016
On Monday, August 01, 2016 14:46:03 Alex via Digitalmars-d-learn wrote:
> On Monday, 1 August 2016 at 13:52:56 UTC, Jonathan M Davis wrote:
> > An array does not implement RandomAccessFinite, which is an interface that you created. So, a function that takes a RandomAccessFinite is not going to accept an array. A dynamic array will match isRandomAccessRange, but that has nothing to do with interfaces.
>
> It's ok for me to say, that some types do not implement an
> interface to show some abilities, even if the interface, which
> has to be implemented to show the same abilities is
> given/known/public/exposed... etc...
> So, I think this part is ok now, I think...

I'm afraid that I don't know what you're talking about, but if what you have is working, then great. But while arrays can pass template constraints, they can't implement interfaces, so what you had before didn't really seem like it was going to work.

> >> But the main question is about the other part, about the
> >> constraint to the first parameter to my functions.
> >> It seems strange to me to use "isIntegral" here, as this is
> >> some
> >> kind of unrelated for something used as an index.
> >> Is there anything more appropriate to check? What kind of
> >> interface/trait has an index to fulfill?
> >
> > What's strange? If you want to accept any integer type, then isIntegral!T would do it. A _better_ thing to do would be to make it so that it's just size_t and not templatize the type, since indices really should be size_t normally (and if the system is 32-bit, then isIntegral!T will accept long and ulong, whereas size_t is uint, and if you pass a long, you'll get a compilation error for arrays, since they take size_t for indexing; it won't matter for 64-bit though, since size_t is ulong there). So,
> >
> > auto f1(R)(size_t index, R range)
> >
> >     if(isRandomAccessRange!R)
> >
> > {
> >
> >     return range[index];
> >
> > }
> >
> > would be better, but aside from the 32-bit issues, isIntegral will work.
> >
> > - Jonathan M Davis
>
> This goes in a different direction I want to. I don't have anything against simplification to size_t, indeed I have it in this way currently. But what I want is something like the following:
>
> having
>      alias MyIndex = int
> and
>      MyIndex s = MyInd(1);
>      writeln(f1(s, arr)); //gives 42, as expected
>
> Now, I want to define a
> struct S
> {
>      int index;
>      ??? what else ??? // alias index this; doesn't help
> }
> and still being able to have
>      alias MyIndex = S
> and nothing else should be changed.

If you want a template constraint that checks that a type works with the index operator on a type, and you're not restricting it to something like size_t, then you're just going to have to check whether the expression is going to compile. Also, that is incompatible with random access ranges. Random access ranges are expected to work with size_t, and while isRandomAccessRange isn't currently quite that restrictive, it does check that r[1] compiles, so the index operator is going to have to accept integers at minimum. If you want a completely generic index operator, and you want a template constraint for it, you're pretty much going to have to test that it compiles. e.g.

auto fun(I, T)(I index, T obj)
    if(__traits(compiles, obj[index]))
{
    return obj[index];
}

or if you want a reusable template, you can do something like

template isIndexable(I, T)
{
    enum isIndexable = __traits(compiles, T.init[I.init]);
}

auto fun(I, T)(I index, T obj)
    if(isIndexable!(I, T))
{
    return obj[index];
}

Personally, I think that having code that accepts any type that can be indexable with any type isn't a particularly good idea, because odds are, it won't actually work, because those different types will work quite differently (e.g. associative arrays are indexable with stuff other than size_t, but they're really not interchangeable with dynamic arrays or static arrays, which use size_t). And anything that wants to be interchangeable with a dynamic array or be usable as a random access range should be using size_t to index. But if you actually have code where it makes sense to deal with types that can take all kinds of random stuff as indices, then something like I posted above should work.

- Jonathan M Davis

August 01, 2016
On Monday, 1 August 2016 at 15:06:54 UTC, Jonathan M Davis wrote:
>
> If you want a template constraint that checks that a type works with the index operator on a type, and you're not restricting it to something like size_t, then you're just going to have to check whether the expression is going to compile. Also, that is incompatible with random access ranges. Random access ranges are expected to work with size_t, and while isRandomAccessRange isn't currently quite that restrictive, it does check that r[1] compiles, so the index operator is going to have to accept integers at minimum. If you want a completely generic index operator, and you want a template constraint for it, you're pretty much going to have to test that it compiles. e.g.
>
> auto fun(I, T)(I index, T obj)
>     if(__traits(compiles, obj[index]))
> {
>     return obj[index];
> }
>
> or if you want a reusable template, you can do something like
>
> template isIndexable(I, T)
> {
>     enum isIndexable = __traits(compiles, T.init[I.init]);
> }
>
> auto fun(I, T)(I index, T obj)
>     if(isIndexable!(I, T))
> {
>     return obj[index];
> }
>
> Personally, I think that having code that accepts any type that can be indexable with any type isn't a particularly good idea, because odds are, it won't actually work, because those

> different types will work quite differently (e.g. associative arrays are indexable with stuff other than size_t, but they're really not interchangeable with dynamic arrays or static arrays, which use size_t). And anything that wants to be interchangeable with a dynamic array or be usable as a random access range should be using size_t to index. But if you

This is exactly what my question is about. I don't think, that it doesn't make sense (and isn't a good idea) to index with an arbitrary type, too.
So, how I can define/design a type, which is not an int/size_t, but has one to be able to index with it?

And if the __compiles trait is the way to go with... well then that's just how it is...
August 01, 2016
On Monday, August 01, 2016 15:25:59 Alex via Digitalmars-d-learn wrote:
> On Monday, 1 August 2016 at 15:06:54 UTC, Jonathan M Davis wrote:
> > If you want a template constraint that checks that a type works with the index operator on a type, and you're not restricting it to something like size_t, then you're just going to have to check whether the expression is going to compile. Also, that is incompatible with random access ranges. Random access ranges are expected to work with size_t, and while isRandomAccessRange isn't currently quite that restrictive, it does check that r[1] compiles, so the index operator is going to have to accept integers at minimum. If you want a completely generic index operator, and you want a template constraint for it, you're pretty much going to have to test that it compiles. e.g.
> >
> > auto fun(I, T)(I index, T obj)
> >     if(__traits(compiles, obj[index]))
> > {
> >     return obj[index];
> > }
> >
> > or if you want a reusable template, you can do something like
> >
> > template isIndexable(I, T)
> > {
> >     enum isIndexable = __traits(compiles, T.init[I.init]);
> > }
> >
> > auto fun(I, T)(I index, T obj)
> >
> >     if(isIndexable!(I, T))
> > {
> >     return obj[index];
> > }
> >
> > Personally, I think that having code that accepts any type that can be indexable with any type isn't a particularly good idea, because odds are, it won't actually work, because those
> >
> > different types will work quite differently (e.g. associative arrays are indexable with stuff other than size_t, but they're really not interchangeable with dynamic arrays or static arrays, which use size_t). And anything that wants to be interchangeable with a dynamic array or be usable as a random access range should be using size_t to index. But if you
>
> This is exactly what my question is about. I don't think, that it doesn't make sense (and isn't a good idea) to index with an arbitrary type, too.

The issue is that the semantics have to be sufficiently similar across the types that the code will work. And if your code is doing much beyond indexing, it probably won't. But if it does in your case, then great.

> So, how I can define/design a type, which is not an int/size_t, but has one to be able to index with it?
>
> And if the __compiles trait is the way to go with... well then that's just how it is...

You're pretty much going to have to go with some form of
__traits(compiles, ...) to test the code that the code that you want to code
compiles. That's ultimately what a lot of traits do. And if what you're
looking to test is that obj[index] works where obj and index are arbitrary
types, what else would you be testing for anyway? You'd only need to test
for more beyond that if you were trying to further restrict the types
involved and/or to require that some other set of operations compiled in
addition to indexing.

- Jonathan M Davis

August 01, 2016
On Monday, 1 August 2016 at 15:51:58 UTC, Jonathan M Davis wrote:
> On Monday, August 01, 2016 15:25:59 Alex via Digitalmars-d-learn wrote:
>> On Monday, 1 August 2016 at 15:06:54 UTC, Jonathan M Davis wrote:
>> > If you want a template constraint that checks that a type works with the index operator on a type, and you're not restricting it to something like size_t, then you're just going to have to check whether the expression is going to compile. Also, that is incompatible with random access ranges. Random access ranges are expected to work with size_t, and while isRandomAccessRange isn't currently quite that restrictive, it does check that r[1] compiles, so the index operator is going to have to accept integers at minimum. If you want a completely generic index operator, and you want a template constraint for it, you're pretty much going to have to test that it compiles. e.g.
>> >
>> > auto fun(I, T)(I index, T obj)
>> >     if(__traits(compiles, obj[index]))
>> > {
>> >     return obj[index];
>> > }
>> >
>> > or if you want a reusable template, you can do something like
>> >
>> > template isIndexable(I, T)
>> > {
>> >     enum isIndexable = __traits(compiles, T.init[I.init]);
>> > }
>> >
>> > auto fun(I, T)(I index, T obj)
>> >
>> >     if(isIndexable!(I, T))
>> > {
>> >     return obj[index];
>> > }
>> >
>> > Personally, I think that having code that accepts any type that can be indexable with any type isn't a particularly good idea, because odds are, it won't actually work, because those
>> >
>> > different types will work quite differently (e.g. associative arrays are indexable with stuff other than size_t, but they're really not interchangeable with dynamic arrays or static arrays, which use size_t). And anything that wants to be interchangeable with a dynamic array or be usable as a random access range should be using size_t to index. But if you
>>
>> This is exactly what my question is about. I don't think, that it doesn't make sense (and isn't a good idea) to index with an arbitrary type, too.
>
> The issue is that the semantics have to be sufficiently similar across the types that the code will work. And if your code is doing much beyond indexing, it probably won't. But if it does in your case, then great.
>
>> So, how I can define/design a type, which is not an int/size_t, but has one to be able to index with it?
>>
>> And if the __compiles trait is the way to go with... well then that's just how it is...
>
> You're pretty much going to have to go with some form of
> __traits(compiles, ...) to test the code that the code that you want to code
> compiles. That's ultimately what a lot of traits do. And if what you're
> looking to test is that obj[index] works where obj and index are arbitrary
> types, what else would you be testing for anyway? You'd only need to test
> for more beyond that if you were trying to further restrict the types
> involved and/or to require that some other set of operations compiled in
> addition to indexing.
>
> - Jonathan M Davis

Got it... Thanks :)
August 03, 2016
On Monday, 1 August 2016 at 16:09:50 UTC, Alex wrote:
> On Monday, 1 August 2016 at 15:51:58 UTC, Jonathan M Davis wrote:
> template isIndexable(I, T)
> {
>     enum isIndexable = __traits(compiles, T.init[I.init]);
> }

As a last question afterwards:
Is it possible to create such an isIndexable template without templating over the type T?
something like

template isIndexable(I)
{
    enum isIndexable = __traits(compiles, ???[I.init]);
}

sure, I could use

__traits(compiles, (int[]).init[I.init])

but is this the intended way to go?
August 03, 2016
On Wednesday, August 03, 2016 09:21:13 Alex via Digitalmars-d-learn wrote:
> On Monday, 1 August 2016 at 16:09:50 UTC, Alex wrote:
> > On Monday, 1 August 2016 at 15:51:58 UTC, Jonathan M Davis
> > wrote:
> > template isIndexable(I, T)
> > {
> >
> >     enum isIndexable = __traits(compiles, T.init[I.init]);
> >
> > }
>
> As a last question afterwards:
> Is it possible to create such an isIndexable template without
> templating over the type T?
> something like
>
> template isIndexable(I)
> {
>      enum isIndexable = __traits(compiles, ???[I.init]);
> }
>
> sure, I could use
>
> __traits(compiles, (int[]).init[I.init])
>
> but is this the intended way to go?

__traits(compiles, ...) is testing whether the code in question compiles. What you need to test is whether the object is indexable by the other, and that would mean needing both the type being indexed and the type which is the index. Just because one particular type is indexable by whatever the index type is doesn't mean that another will be. For instance, int[] is only going to be indexable by size_t and anything that implicitly converts to size_t. Testing whether int[] is indexable with size_t isn't going to say anything about whether int[string] is indexable with size_t. If you want to test that int[string] is indexable with size_t, you'll need to actually test it with size_t, not int[] - the same goes for any other combinaton of object to index and object that is the index. If you're just typing out the whole thing every time, then you can do stuff like

auto foo(I, T)(I index, T obj)
    if(__traits(compiles, obj[index]))
{
    ...
}

because you have actual objects to deal with, whereas with a template written to encapsulate that test, you only have the types. The init property is just an easy way to get at an object of the type without declaring it or worrying about how one is constructed. But to do the same test as that example with a separate template, you're going to need both types. e.g.

template isIndexable(I, T)
{
    enum isIndexable = __traits(compiles, T.init[I.init]);
}

If you don't have both, you're not doing the same test, and it's impossible to test that one type is indexable by another without using both types in the test. To only have one of the two types in a test would be like trying t to test whether a function can be called on a particular type while only having the function in the test and not the type. It doesn't work.

- Jonathan M Davis
August 03, 2016
On Wednesday, 3 August 2016 at 14:23:59 UTC, Jonathan M Davis wrote:
> On Wednesday, August 03, 2016 09:21:13 Alex via Digitalmars-d-learn wrote:
>> On Monday, 1 August 2016 at 16:09:50 UTC, Alex wrote:
>> > On Monday, 1 August 2016 at 15:51:58 UTC, Jonathan M Davis
>> > wrote:
>> > template isIndexable(I, T)
>> > {
>> >
>> >     enum isIndexable = __traits(compiles, T.init[I.init]);
>> >
>> > }
>>
>> As a last question afterwards:
>> Is it possible to create such an isIndexable template without
>> templating over the type T?
>> something like
>>
>> template isIndexable(I)
>> {
>>      enum isIndexable = __traits(compiles, ???[I.init]);
>> }
>>
>> sure, I could use
>>
>> __traits(compiles, (int[]).init[I.init])
>>
>> but is this the intended way to go?
>
> __traits(compiles, ...) is testing whether the code in question compiles. What you need to test is whether the object is indexable by the other, and that would mean needing both the type being indexed and the type which is the index. Just because one particular type is indexable by whatever the index type is doesn't mean that another will be. For instance, int[] is only going to be indexable by size_t and anything that implicitly converts to size_t. Testing whether int[] is indexable with size_t isn't going to say anything about whether int[string] is indexable with size_t. If you want to test that int[string] is indexable with size_t, you'll need to actually test it with size_t, not int[] - the same goes for any other combinaton of object to index and object that is the index. If you're just typing out the whole thing every time, then you can do stuff like
>
> auto foo(I, T)(I index, T obj)
>     if(__traits(compiles, obj[index]))
> {
>     ...
> }
>
> because you have actual objects to deal with, whereas with a template written to encapsulate that test, you only have the types. The init property is just an easy way to get at an object of the type without declaring it or worrying about how one is constructed. But to do the same test as that example with a separate template, you're going to need both types. e.g.
>
> template isIndexable(I, T)
> {
>     enum isIndexable = __traits(compiles, T.init[I.init]);
> }
>
> If you don't have both, you're not doing the same test, and it's impossible to test that one type is indexable by another without using both types in the test. To only have one of the two types in a test would be like trying t to test whether a function can be called on a particular type while only having the function in the test and not the type. It doesn't work.
>
> - Jonathan M Davis

But it should.

Just found this:
https://www.sgi.com/tech/stl/StrictWeakOrdering.html
which should be fulfilled by a type, which can be used as a key. So, in my opinion, the property "being a key" is a matter of an abstract definition and should have nothing to do with objects which I want to index with it.

Sure, the object which I want to index, has to define an opIndex method, which can receive the key-thing, but this is already the next step.
« First   ‹ Prev
1 2