Mutable enums

Timon Gehr timon.gehr at gmx.ch
Thu Nov 17 11:00:48 PST 2011


On 11/17/2011 07:23 PM, Steven Schveighoffer wrote:
> On Thu, 17 Nov 2011 12:31:58 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:
>
>> On 11/17/2011 03:19 PM, Steven Schveighoffer wrote:
>
>>>
>>> What does writelnInferConst!T do? I'm afraid I'm not getting what you
>>> are saying.
>>>
>>> I was thinking writeln should do this:
>>>
>>> void writeln(T...)(const T args) {...}
>>
>> As you pointed out, this cannot print types that have a non-const
>> toString method (caching the result could be a perfectly valid reason
>> for that.)
>
> Caching string representation IMO is not a significant use case. Not
> only that, but toString should be deprecated anyways in favor of a
> stream-based system.
>
>> writelnInferConst finds out which parameters can be treated as const
>> and still be printed so that the correct version of writeln may be
>> called.
>>
>> For example:
>>
>> class Foo{ // can be printed if const
>> string toString()const{return "Foo";}
>> }
>>
>> class Bar{ // cannot be printed if const
>> string cache;
>> string toString(){return cache!is null?cache:(cache="Bar");}
>> }
>>
>> template hasConstToString(T){
>> enum hasConstToString = is(typeof((const T t){return t.toString();}));
>> }
>>
>> template writelnInferConstImpl(T...){
>> static if(!T.length) alias T X;
>> else static if(hasConstToString!(T[0])){
>> alias T[0] _;
>> alias TypeTuple!(const(_),writelnInferConst!(T[1..$])) X;
>> }else
>> alias TypeTuple!(T[0],writelnInferConst!(T[1..$])) X;
>> }
>> template writelnInferConst(T...){alias writelnInferConstImpl!T.X
>> writelnInferConst;} // (bug 6966)
>>
>>
>> static
>> assert(is(writelnInferConst!(Foo,Bar,Foo,Foo,Bar)==TypeTuple!(const(Foo),Bar,const(Foo),const(Foo),Bar)));
>>
>
> If your goal is to reduce template bloat, I think this is not the solution.
>
> But also note that this still does not guarantee const. I don't really
> see the point of doing all these templates if you aren't going to
> guarantee writeln doesn't modify the data.
>
>>>
>>> The issue with all this is, IFTI doesn't work that way:
>>>
>>> void foo(T: short)(T t) {}
>>>
>>> void main()
>>> {
>>> foo(1);
>>> }
>>>
>>> testifti.d(5): Error: template testifti.foo(T : short) does not match
>>> any function template declaration
>>> testifti.d(5): Error: template testifti.foo(T : short) cannot deduce
>>> template function from argument types !()(int)
>>>
>>> IFTI decides the types of literals before trying to find a proper
>>> template to instantiate. Any possibility to intercept the decision of
>>> literal type or of instantiation would be useful. I think that it's
>>> better suited to the constraints, because there is more power there. But
>>> I'm not sure. If you can find a more straightforward way, I'm all for
>>> it.
>>>
>>> In any case, I need to update the bug report, because the general case
>>> is if foo has an overload. For instance:
>>>
>>> foo(short s);
>>> foo(wstring w);
>>>
>>> foo2 should be able to call both with 1 and "hello" without issue.
>>>
>>> My driving use case to create the enhancement was creating a wrapper
>>> type that intercepted function calls. I planned to use opDispatch, but
>>> it didn't quite work with literals.
>>
>> Ok, I see the problem. My proposed IFTI template mechanism would save
>> the day.
>>
>> It would look like this (static foreach would have to be replaced by a
>> recursive mixin template because Walter encountered implementation
>> difficulties).
>>
>>
>> template OverloadsOf(alias symbol){ // should this be in std.traits?
>> alias TypeTuple!(__traits(getOverloads, __traits(parent,symbol),
>> __traits(identifier,symbol))) OverloadsOf;
>> }
>>
>> auto wrapper(alias foo)(ParameterTypeTuple!foo args){
>> return foo(args);
>> }
>> template opDispatch(string op,T...)(T){
>> static foreach(foo; OverloadsOf!(mixin(op))){
>> alias wrapper!foo opDispatch;
>> }
>> static if(OverloadsOf!(mixin(op)).length==0) { // we are dealing with
>> a template function
>> auto opDispatch(T args){
>> return foo(args);
>> }
>> }
>> }
>
> Pardon my saying so, but this looks horrendous. Not to mention that I
> don't think it would work.

Oh, it would certainly work.

> IFTI instantiates templates, it does not look
> inside instantiated templates for overloads.

This works, does this solve the confusion?:

void foo(int){writeln("foo!");}
void bar(double){writeln("bar!");}

template merge(){
     alias foo qux;
     alias bar qux;
}
alias merge!().qux qux;

void main(){
     qux(1);   // calls foo
     qux(1.0); // calls bar
}



>
> BTW, your proposed IFTI template mechanism, do you have it stated
> anywhere? Maybe it fixes the problem I mentioned.

Not yet, I will file a bugzilla enhancement request and post a link here.

What it does is quite simple:

1. Apply normal IFTI instantiation rules to the IFTI template, as if it 
was a function template.
2. Instantiate the IFTI template with the deduced arguments.
3. The result of the instantiation must be callable with the original 
arguments. Call it.

This allows the function that is called to have a different (albeit 
compatible) signature from what IFTI would give you.


>
>>> Seems rather odd you should have to jump through these hoops to get the
>>> compiler to use ROM space. But I concede that I did not know of this
>>> trick. It does not sway my opinion that CTFE should produce ROM-stored
>>> references, and library function should be used for runtime-constructed
>>> references.
>>>
>>
>> CTFE should produce ROM-stored data iff it is used during run time, I
>> agree on that. However if the enum is typed as mutable, it should
>> create a copy of the ROM-stored data.
>
> Well, this is closer than I thought we were to an agreement. I would
> agree with this, as long as it could be implicitly cast to immutable or
> const.
>
> i.e.:
>
> enum foo = [1, 2, 3];
>
> immutable(int)[] a = foo; // no allocation
> const(int)[] b = foo; // no allocation
>
> -Steve

Yes, that is exactly how I would imagine it to work.




More information about the Digitalmars-d-learn mailing list