January 20, 2007
Dave wrote:
> Christian Kamm wrote:
>> If inout is used, it doesn't guarantee B) and even worse: I'm lying to anyone reading the function signature. A pointer will also require 
> 
> I agree, I don't like the thought of us having to use inout as an optimization. That's not what it's meant for. Leaving aside the notion of 'const' right now, I think 'byref' or some such would work fine for this purpose. It tells a function user "this is passed by reference" and is easily grep-able (unlike '&').
> 
> And a pointer will require the use of '&' at every call site instead of 'byref' just once in the function definition.

So you're suggesting byref would be a synonym for inout?  And the compiler would treat it exactly like the current inout?  If the compiler's not going to be of any help in making sure that I don't change things that I don't mean to be changing, then I don't see much point in introducing a new keyword.

--bb
January 20, 2007
Chris Nicholson-Sauls wrote:
> Bill Baxter wrote:
>> Walter Bright wrote:
>>> Bill Baxter wrote:
>>>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>>>> A) efficient and
>>>> B) guaranteed not to change the caller's value
>>>>
>>>> In C++ the answer is const LargeStruct&.
>>>
>>> C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.
>>
>> Very true.  What I'm after is really a concise way to both
>> a) document to readers of the code that it won't change and
>> b) get the compiler to zap me if I accidentally try to modify it.
>>
>>>> In D the answer is...
>>>
>>> There aren't const references in D. But the 3 ways to pass it by pointer are:
>>>
>>> 1) inout
>>> 2) take the address and use a pointer
>>> 3) make it a class instead
>>
>> And none of those accomplishes a or b.
>>
>> --bb
> 
> Just a thought: B) can be accomplished with an Interface that only exposes gettors.
> Granted, though, that 1) it means defining everything you want to be read-only as a
> property, but these would be optimized away by inlining anyhow, 2) you have to write an
> Interface (which isn't a big deal, IMHO, but I know some people can be annoyed with them).
>  Also, you would want to make the implementor class and all methods 'final' to avoid
> potential virtuality overhead.
> 
> Something akin to:
> <code>
> interface IFoo {
>   int alpha () ;
>   int beta  () ;
> }
> 
> final class Foo : IFoo {
>   private {
>     int m_alpha ;
>     int m_beta  ;
>   }
> 
>   final int alpha () { return m_alpha ; }
>   final int beta  () { return m_beta  ; }
> 
>   final int alpha (int x) { return m_alpha = x ; }
>   final int beta  (int x) { return m_beta  = x ; }
> }
> 
> void somefunc (IFoo obj) {
>   // do stuff with obj.alpha and obj.beta
> }
> 
> auto myfoo = new Foo;
> somefunc(myfoo);
> </code>
> 
> -- Chris Nicholson-Sauls

That's pretty clever, but it's an awful lot superfluous cruft for something that should just be as simple as

struct Foo
{
   int m_alpha, m_beta;
}

Reminds me of a quote I heard once about knitting needles and eyeballs.

--bb
January 20, 2007
Bill Baxter wrote:
> Dave wrote:
>> Christian Kamm wrote:
>>> If inout is used, it doesn't guarantee B) and even worse: I'm lying to anyone reading the function signature. A pointer will also require 
>>
>> I agree, I don't like the thought of us having to use inout as an optimization. That's not what it's meant for. Leaving aside the notion of 'const' right now, I think 'byref' or some such would work fine for this purpose. It tells a function user "this is passed by reference" and is easily grep-able (unlike '&').
>>
>> And a pointer will require the use of '&' at every call site instead of 'byref' just once in the function definition.
> 
> So you're suggesting byref would be a synonym for inout?  And the compiler would treat it exactly like the current inout?  If the compiler's not going to be of any help in making sure that I don't change things that I don't mean to be changing, then I don't see much point in introducing a new keyword.
> 

'inout' doesn't necessarily specify 'byref'. But, in C++ if you specify '&' then the param. must be passed by reference. 'byref' would act like that. 'byref' doesn't have to satisfy the IDL requirement like inout does either.

I agree that if it could be made to work, 'byref' acting like a semantically meaningful C++ 'const&' would be great (then  maybe 'cref' for the keyword?).

D is terrific - truly a better C++ while avoiding almost all of C++'s corner cases. But IMHO D takes this a little too far without reference params. and being able to return references (and ctors for structs while I'm at it). They are used so often in C++ I find it hard to justify a language design for 'a better C++' that doesn't match those capabilities.

> --bb
January 20, 2007
Bill Baxter wrote:
> Walter Bright wrote:
>> Bill Baxter wrote:
>>> The question is how can we write functions in D that take big structs as parameters in such a way that it is:
>>> A) efficient and
>>> B) guaranteed not to change the caller's value
>> There aren't const references in D. But the 3 ways to pass it by pointer are:
>>
>> 1) inout
>> 2) take the address and use a pointer
>> 3) make it a class instead
> 
> And none of those accomplishes a or b.

All of them accomplish A.
January 20, 2007
janderson wrote:
> Walter Bright wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> No, and for a good reason:
>>>
>>> foo(LargeStruct s1, LargeStruct s2)
>>> {
>>>   ...
>>> }
>>> ...
>>> LargeStruct s;
>>> foo(s, s);
>>>
>>> Nobody would enjoy hidden aliasing of s1 and s2.
> 
> I think you mean more like:
> 
> 
> foo(LargeStruct s1, LargeStruct inout s2)
> {
>    ...
> }
> ...
> LargeStruct s;
> foo(s, s);

I meant what I wrote, although your example is just as relevant. If the compiler takes initiative in passing things by reference, there is risk of unintended aliasing.


Andrei
January 20, 2007
Chris Nicholson-Sauls wrote:
> Just a thought: B) can be accomplished with an Interface that only exposes gettors.
[snip]
> <code>
> interface IFoo {
>   int alpha () ;
>   int beta  () ;
> }
> 
> final class Foo : IFoo {
>   private {
>     int m_alpha ;
>     int m_beta  ;
>   }
> 
>   final int alpha () { return m_alpha ; }
>   final int beta  () { return m_beta  ; }
> 
>   final int alpha (int x) { return m_alpha = x ; }
>   final int beta  (int x) { return m_beta  = x ; }
> }
> 
> void somefunc (IFoo obj) {
>   // do stuff with obj.alpha and obj.beta
> }
> 
> auto myfoo = new Foo;
> somefunc(myfoo);
> </code>

I think this is very interesting because it opens the door to a new
implementation of const that I never thought of.

D is slated to have full-fledged compile-time reflection, meaning that
you can find out all information about any given symbol, during
compilation. So it turns out that a possible path to implement const is
to make it a template. Given an arbitrary struct Foo, you could define a
template called Const such that Const!(Foo) materializes into a struct
that holds a Foo* inside, and exposes only the non-mutating parts of Foo.

So, given:

struct Foo
{
  int alpha, beta;
  void nonmutator() { assert(alpha * beta != 0); }
  void mutator() { alpha = beta; }
}

the template Const!(Foo) generates the code:

struct Const!(Foo)
{
  private Foo* pFoo;
  this(inout Foo foo) { pFoo = &foo; }
  int alpha() { return pFoo->alpha; }
  int beta() { return pFoo->beta; }
  void nonmutator() { pFoo->nonmutator(); }
}

The reflection mechanism would have to provide the information whether
or not a given member function changes the object.

The only drawback that I can think right now is that the compiler can't
exploit this kind of constness with ease to generate better code; it's a
"user-space" implementation with semantics that are hard to figure out
at the compiler level.

A minor drawback is that Const!(Foo) must be implicitly constructible
from a Foo, but another in-design language feature (opImplicitCast) will
take care of that.


Andrei
January 20, 2007
Andrei Alexandrescu (See Website for Email) wrote:
> Chris Nicholson-Sauls wrote:
>> Just a thought: B) can be accomplished with an Interface that only exposes gettors.
> [snip]
>> <code>
>> interface IFoo {
>>   int alpha () ;
>>   int beta  () ;
>> }
>>
>> final class Foo : IFoo {
>>   private {
>>     int m_alpha ;
>>     int m_beta  ;
>>   }
>>
>>   final int alpha () { return m_alpha ; }
>>   final int beta  () { return m_beta  ; }
>>
>>   final int alpha (int x) { return m_alpha = x ; }
>>   final int beta  (int x) { return m_beta  = x ; }
>> }
>>
>> void somefunc (IFoo obj) {
>>   // do stuff with obj.alpha and obj.beta
>> }
>>
>> auto myfoo = new Foo;
>> somefunc(myfoo);
>> </code>
> 
> I think this is very interesting because it opens the door to a new
> implementation of const that I never thought of.
> 
> D is slated to have full-fledged compile-time reflection, meaning that
> you can find out all information about any given symbol, during
> compilation. So it turns out that a possible path to implement const is
> to make it a template. Given an arbitrary struct Foo, you could define a
> template called Const such that Const!(Foo) materializes into a struct
> that holds a Foo* inside, and exposes only the non-mutating parts of Foo.
> 
> So, given:
> 
> struct Foo
> {
>   int alpha, beta;
>   void nonmutator() { assert(alpha * beta != 0); }
>   void mutator() { alpha = beta; }
> }
> 
> the template Const!(Foo) generates the code:
> 
> struct Const!(Foo)
> {
>   private Foo* pFoo;
>   this(inout Foo foo) { pFoo = &foo; }
>   int alpha() { return pFoo->alpha; }
>   int beta() { return pFoo->beta; }
>   void nonmutator() { pFoo->nonmutator(); }
> }
> 
> The reflection mechanism would have to provide the information whether
> or not a given member function changes the object.
> 
> The only drawback that I can think right now is that the compiler can't
> exploit this kind of constness with ease to generate better code; it's a
> "user-space" implementation with semantics that are hard to figure out
> at the compiler level.
> 
> A minor drawback is that Const!(Foo) must be implicitly constructible
> from a Foo, but another in-design language feature (opImplicitCast) will
> take care of that.
> 
> 
> Andrei

Its not a bad idea, although with a little more flavor added to Tuples, template Const might become more general and therefore more usable.  Particularly if we could get an 'identifier' construct in templates such as has been asked for before, and specializations that mimic what 'is' expressions can do, and a true "static foreach".  (I know, its a long list of wants.)

struct Const (T : struct) {
  private T* ptr ;

  static Const!(T) opCall (inout T t) { ptr = &t; }

  static foreach (field; T.tupleof) {
    typeof(field) identifier(field.nameof) () { return *ptr.field; }
  }
}

struct Foo {
  int alpha, beta;
}

Foo myfoo;
auto cfoo = Const!(Foo)(myfoo);

It loses the mapping to functions, though.  I almost have to admit a 'const' like parameter storage class might well be the way to go.  Either the 'byref' that has been mentioned previously, or a reuse of either 'static' or (*sigh*) 'const'.

-- Chris Nicholson-Sauls
January 20, 2007
Andrei Alexandrescu (See Website for Email) wrote:
> Chris Nicholson-Sauls wrote:
>> Just a thought: B) can be accomplished with an Interface that only exposes gettors.
> [snip]
>> <code>
>> interface IFoo {
>>   int alpha () ;
>>   int beta  () ;
>> }
>>
>> final class Foo : IFoo {
>>   private {
>>     int m_alpha ;
>>     int m_beta  ;
>>   }
>>
>>   final int alpha () { return m_alpha ; }
>>   final int beta  () { return m_beta  ; }
>>
>>   final int alpha (int x) { return m_alpha = x ; }
>>   final int beta  (int x) { return m_beta  = x ; }
>> }
>>
>> void somefunc (IFoo obj) {
>>   // do stuff with obj.alpha and obj.beta
>> }
>>
>> auto myfoo = new Foo;
>> somefunc(myfoo);
>> </code>
> 
> I think this is very interesting because it opens the door to a new
> implementation of const that I never thought of.
> 
> D is slated to have full-fledged compile-time reflection, meaning that
> you can find out all information about any given symbol, during
> compilation. So it turns out that a possible path to implement const is
> to make it a template. Given an arbitrary struct Foo, you could define a
> template called Const such that Const!(Foo) materializes into a struct
> that holds a Foo* inside, and exposes only the non-mutating parts of Foo.
> 
> So, given:
> 
> struct Foo
> {
>   int alpha, beta;
>   void nonmutator() { assert(alpha * beta != 0); }
>   void mutator() { alpha = beta; }
> }
> 
> the template Const!(Foo) generates the code:
> 
> struct Const!(Foo)
> {
>   private Foo* pFoo;
>   this(inout Foo foo) { pFoo = &foo; }
>   int alpha() { return pFoo->alpha; }
>   int beta() { return pFoo->beta; }
>   void nonmutator() { pFoo->nonmutator(); }
> }
> 

But you could still trivially and legally subvert this 'const'-ness:

    Const!(Foo) c(Foo(100,200));
    Foo* fp = *cast(Foo**)&c;
    fp.alpha = 10000;
    writefln(c.alpha);

So according to what Walter has said in the past about 'semantically meaningful const' then it wouldn't be enough to base optimizations on (even if the compiler could glean useful information from this).

> The reflection mechanism would have to provide the information whether
> or not a given member function changes the object.
> 
> The only drawback that I can think right now is that the compiler can't
> exploit this kind of constness with ease to generate better code; it's a
> "user-space" implementation with semantics that are hard to figure out
> at the compiler level.
> 

Both "user-space" and "compiler-space" const has been discussed at length in the past, and Walter's response has always been (paraphrasing) "D won't have const unless it is semantically meaningful (100% enforceable) by the compiler", which will never happen with a language like D.

> A minor drawback is that Const!(Foo) must be implicitly constructible
> from a Foo, but another in-design language feature (opImplicitCast) will
> take care of that.
> 
> 
> Andrei
January 20, 2007
Walter Bright wrote:
> Bill Baxter wrote:
>> The question is how can we write functions in D that take big structs
>> as parameters in such a way that it is:
>> A) efficient and
>> B) guaranteed not to change the caller's value
>>
>> In C++ the answer is const LargeStruct&.
> 
> C++ doesn't *guarantee* it won't change. There are at least two legal ways to do it.

This is an oft-repeated point, but it also misses the users' perspective.  It's important from an implementors perspective that this use of const as a type modifier doesn't provide the compiler with a guarantee of immutability.  Given sensible users, that doesn't generally matter to programmers writing code.  const documents an interface; it doesn't enforce it, though

>> In D the answer is...
> 
> There aren't const references in D. But the 3 ways to pass it by pointer are:
> 
> 1) inout
> 2) take the address and use a pointer
> 3) make it a class instead

So, in D the answer is that you have to provide a wrapper
which provides no update operations?  Very heavyweight for
a simple, useful thing that C++ can express pretty well
inside the language.  Users like it; they mostly find that
it does what they like.  Java, C#, D and Felix have dropped
this valuable feature from C++, and users miss it.  const
in C++ is far from perfect, but it's much, much more useful
than having no answer other than "make a copy and maybe
the compiler will be able to optimize it away".  Sometimes
we really do want reference-to-const semantics.

Granted, this adds some complexity to the language, and
other languages have flourished without anything like the
C++ notion of const as a qualifier on pointers/references.
But it's important to recognize that this shouldn't be
dismissed by noting that const (apart from top-level
const) doesn't provide much useful guarantee to a compiler
write; it *does* provide something very useful to
programmers using a language.

-- James

January 20, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> janderson wrote:
>> Walter Bright wrote:
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> No, and for a good reason:
>>>>
>>>> foo(LargeStruct s1, LargeStruct s2)
>>>> {
>>>>   ...
>>>> }
>>>> ...
>>>> LargeStruct s;
>>>> foo(s, s);
>>>>
>>>> Nobody would enjoy hidden aliasing of s1 and s2.
>>
>> I think you mean more like:
>>
>>
>> foo(LargeStruct s1, LargeStruct inout s2)
>> {
>>    ...
>> }
>> ...
>> LargeStruct s;
>> foo(s, s);
> 
> I meant what I wrote, although your example is just as relevant. If the compiler takes initiative in passing things by reference, there is risk of unintended aliasing.
> 
> 
> Andrei

If all the function does is read the values.  How can aliasing be a problem?

-Joel