Jump to page: 1 2
Thread overview
Templates class member functions not conditional?
Sep 11, 2012
monarch_dodra
Sep 11, 2012
bearophile
Sep 11, 2012
monarch_dodra
Sep 11, 2012
bearophile
Sep 11, 2012
monarch_dodra
Sep 11, 2012
bearophile
Sep 11, 2012
monarch_dodra
Sep 11, 2012
monarch_dodra
Sep 11, 2012
Jonathan M Davis
Sep 11, 2012
monarch_dodra
Sep 11, 2012
monarch_dodra
Sep 11, 2012
monarch_dodra
Sep 11, 2012
Jonathan M Davis
Sep 11, 2012
monarch_dodra
Sep 11, 2012
Jonathan M Davis
Sep 12, 2012
monarch_dodra
September 11, 2012
This is related to:
http://d.puremagic.com/issues/show_bug.cgi?id=5193

Basically:
-------
struct S
{
  const int i;
}

struct C(T)
{
    private T val;
    @property void front(T value)
        {val = value;} //HERE
}

void main()
{
  C!S test;
}
--------

I wrote a generic template C(T), with a certain member function. I created an instance of that template with the parameter S.

Now, I'm getting a compile error at here, which would be understandable...
...*if* I was calling said member function.

In C++, member functions of templates are *only* compiled if they are ever called. This is not just an optimization, it is meant specifically to allow compiling a template, only if a certain amount of functionality is required, and the "not required" functionality wouldn't compile anyways.

Is this not the case for D? Or is it currently a limitation?
Can I ever expect we'll get a "conditionally compiled on requirement" functionality for template struct member functions.


Regarding making the above work, I found 2 solutions:

/////////////////////  1  /////////////////////
struct C(T)
{
    private T val;
    static if(isAssignable(T!T))
    {
        @property void front(T value)
            {val = value;}
    }
}

This works but:
a) It looks cludgy, and cumbursome on implementation
b) If I *were* to attemp a call to front, the compile error would be an obscure "fucntion not found", as opposed to "can't assign"

/////////////////////  2  /////////////////////
I find this more elegant: Make the member function itself a template:

struct C(T)
{
    private T val;
    @property void front()(T value)
        {val = value;}
}

This works, and is correctly "conditionally compiled on requirement". The signature is kind of kludgy, but it works... AND, if someone *does* attempt to make the call, then a verbose compile error appears.

Thoughts?
September 11, 2012
monarch_dodra:

> Is this not the case for D? Or is it currently a limitation?

In this case I think D is working as designed. All functions inside a template are created when you instantiate it.


> Can I ever expect we'll get a "conditionally compiled on requirement" functionality for template struct member functions.

I have asked for a @templated(Arg1, Arg2, ...) that's usable for this purpose too, but Walter has not commented so far.


> struct C(T)
> {
>     private T val;
>     static if(isAssignable(T!T))
>     {
>         @property void front(T value)
>             {val = value;}
>     }
> }

Probably such use of a static if (or debug, version, etc) is the idiomatic way to do what you want in D. It's easy to see and understand for the person that reads the code.


> struct C(T)
> {
>     private T val;
>     @property void front()(T value)
>         {val = value;}
> }
>
> This works, and is correctly "conditionally compiled on requirement". The signature is kind of kludgy, but it works... AND, if someone *does* attempt to make the call, then a verbose compile error appears.

This doesn't look bad, just remember this doesn't work:


struct C(T) {
    private T val;
    @property void front()(T value) {
        val = value;
    }
}
void main() {
    C!int ci;
    auto f = &ci.front;
    assert(ci.val == 0);
    f(1);
    assert(ci.val == 1);
}

Bye,
bearophile
September 11, 2012
On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
> This doesn't look bad, just remember this doesn't work:
>
>
> struct C(T) {
>     private T val;
>     @property void front()(T value) {
>         val = value;
>     }
> }
> void main() {
>     C!int ci;
>     auto f = &ci.front;
>     assert(ci.val == 0);
>     f(1);
>     assert(ci.val == 1);
> }
>
> Bye,
> bearophile

True, but this does: auto f = &ci.front!();

That said, it looks horrible, and, client code should not be affected in such a way. So yeah, not a great solution. Plus, it creates a new semantic which is not very obvious.

I'll have to admit, I'm really not a fan of of such explicit conditional implementations. It really burdens the code, and forces the implementer to think about the required conditions to use the function, rather than let the compiler issue a failure when it just *does* happen fail.

I am opening an enhancement request for this.
September 11, 2012
On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
> monarch_dodra:
>
>> Is this not the case for D? Or is it currently a limitation?
>
> In this case I think D is working as designed. All functions inside a template are created when you instantiate it.
>
> [SNIP]
>
> Bye,
> bearophile

Well, the functions are declared inside the template, but do they have to be validated and compiled?

I mean, technically, in C++, the functions are also there, just not compiled and validated...
September 11, 2012
monarch_dodra:

> and forces the implementer to think about the required conditions to use the function,

I think this is usually considered a good practice in D, just like using template constraints.

If you look in Phobos, similar situations are handled with static ifs. As example see the several static if used inside MapResult() to disable some of its features:

https://github.com/D-Programming-Language/phobos/blob/master/std/algorithm.d#L384


private struct MapResult(alias fun, Range)
{
    alias Unqual!Range R;
    //alias typeof(fun(.ElementType!R.init)) ElementType;
    R _input;

    static if (isBidirectionalRange!R)
    {
        @property auto ref back()
        {
            return fun(_input.back);
        }

        void popBack()
        {
            _input.popBack();
        }
    }

    this(R input)
    {
        _input = input;
    }

    static if (isInfinite!R)
    {
        // Propagate infinite-ness.
        enum bool empty = false;
    }
    else
    {
        @property bool empty()
        {
            return _input.empty;
        }
    }

    void popFront()
    {
        _input.popFront();
    }

    @property auto ref front()
    {
        return fun(_input.front);
    }

    static if (isRandomAccessRange!R)
    {
        static if (is(typeof(_input[ulong.max])))
            private alias ulong opIndex_t;
        else
            private alias uint opIndex_t;

        auto ref opIndex(opIndex_t index)
        {
            return fun(_input[index]);
        }
    }

    static if (hasLength!R || isSomeString!R)
    {
        @property auto length()
        {
            return _input.length;
        }

        alias length opDollar;
    }

    static if (hasSlicing!R)
    {
        static if (is(typeof(_input[ulong.max .. ulong.max])))
            private alias ulong opSlice_t;
        else
            private alias uint opSlice_t;

        auto opSlice(opSlice_t lowerBound, opSlice_t upperBound)
        {
            return typeof(this)(_input[lowerBound..upperBound]);
        }
    }

    static if (isForwardRange!R)
    {
        @property auto save()
        {
            auto result = this;
            result._input = result._input.save;
            return result;
        }
    }
}


Bye,
bearophile
September 11, 2012
On Tuesday, 11 September 2012 at 13:33:08 UTC, bearophile wrote:
> monarch_dodra:
>
>> and forces the implementer to think about the required conditions to use the function,
>
> I think this is usually considered a good practice in D, just like using template constraints.
>
> If you look in Phobos, similar situations are handled with static ifs. As example see the several static if used inside MapResult() to disable some of its features:
>
> [SNIP]
>
> Bye,
> bearophile

I'd argue it's horrible! Why should the coder bother making these conditional, if calling them would have failed anyways?

The fact that it is done like this in D, I'd argue, is by necessity, not necessarily by good practice.

IMO, being able to do this is a much better alternative:

    @property auto save()
    {
        static assert(isForwardRange!R); //Optional
        auto result = this;
        result._input = result._input.save;
        return result;
    }

It is cleaner and easier on the developer. The exact line that fails is reported (as opposed to "member not found"). An optional static assert can make the reason *why* it failed be bloody explicit.

The last reason why this approach is superior, is because above, "front(T)" *does* exist, but it is implemented as "can't work", and calling it MUST fail. This is different from not having it implemented, which allows a third party to define it via UFCS. In that case though, it would not be enriching the interface, it would be hijacking it.
September 11, 2012
monarch_dodra:

> I'd argue it's horrible! Why should the coder bother making these conditional, if calling them would have failed anyways?

If you see, some of those static ifs also have an else clause.

Bye,
bearophile
September 11, 2012
On Tuesday, 11 September 2012 at 15:35:33 UTC, bearophile wrote:
> monarch_dodra:
>
>> I'd argue it's horrible! Why should the coder bother making these conditional, if calling them would have failed anyways?
>
> If you see, some of those static ifs also have an else clause.
>
> Bye,
> bearophile

The only one I really see is for "empty", but I don't really see how that changes anything? That is actually the *only* valid case I can see where usage of a static if is legitimate.

September 11, 2012
On Tuesday, September 11, 2012 15:12:57 monarch_dodra wrote:
> On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
> > monarch_dodra:
> >> Is this not the case for D? Or is it currently a limitation?
> > 
> > In this case I think D is working as designed. All functions inside a template are created when you instantiate it.
> > 
> > [SNIP]
> > 
> > Bye,
> > bearophile
> 
> Well, the functions are declared inside the template, but do they have to be validated and compiled?
> 
> I mean, technically, in C++, the functions are also there, just not compiled and validated...

Anything inside a template is not compiled unless the template is instantiated, but if the template is instantiated, the entire template is instantiated and compiled (save for static if branches which aren't followed or inner templates which aren't instantiated). If C++ doesn't work that way as well, I find that to be very bizarre. Regardless though, that's how D works.

- Jonathan M Davis
September 11, 2012
On Tuesday, 11 September 2012 at 15:43:56 UTC, Jonathan M Davis wrote:
>
> Anything inside a template is not compiled unless the template is
> instantiated, but if the template is instantiated, the entire template is
> instantiated and compiled (save for static if branches which aren't followed
> or inner templates which aren't instantiated). If C++ doesn't work that way as
> well, I find that to be very bizarre. Regardless though, that's how D works.
>
> - Jonathan M Davis

TY for putting up with me :D

C++ works a certain way, and I find it very bizarre that D doesn't work that way either. Then again, C++ doesn't have static if.



At the very least, I wish we could write:
--------
struct S(T)
{
    @property void front(T value)
        if(isAssignable!(T,T))
    {
        ...
    }
}
--------
That would make things less bulky and the intent much more streamlined with what we already have.

In this specific case, this doesn't work because "front" itself isn't a template, but given the encapsulating struct *is* a template, I think it should be made to work.

As a matter of fact, I don't see why we can't use conditional implementations ALL the time? Like: If some enum is defined, or if a certain type works or whatever.
« First   ‹ Prev
1 2