www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Returning const ref (structs) in D

reply "Stian" <stian.pedersen gmail.com> writes:
Okay, Im trying to wrap my head around how i can return a 
reference from a class.
Lets say I have some class Foo that stores a struct Bar that 
stores many bytes.

In C, i would have a function

const Bar & getBar(){return bar;}

In code, i could get a (const) reference to bar using
const Bar & v = foo.getBar();
or i may copy it, if i need to use it for something else
Bar v = foo.getBar();

How can i do the same in D? The manuals aren't crystal clear on 
this matter. Using in in parameter lists will give me a const 
reference, so that i avoid input copying, if I understand 
correctly.

But i want get const aliases to structs, and if i should pass 
that alias into a variable storing a struct, then i would want 
the copy semantics.
Nov 24 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, November 24, 2012 21:46:41 Stian wrote:
 Okay, Im trying to wrap my head around how i can return a
 reference from a class.
 Lets say I have some class Foo that stores a struct Bar that
 stores many bytes.
 
 In C, i would have a function
 
 const Bar & getBar(){return bar;}
 
 In code, i could get a (const) reference to bar using
 const Bar & v = foo.getBar();
 or i may copy it, if i need to use it for something else
 Bar v = foo.getBar();
 
 How can i do the same in D? The manuals aren't crystal clear on
 this matter. Using in in parameter lists will give me a const
 reference, so that i avoid input copying, if I understand
 correctly.
 
 But i want get const aliases to structs, and if i should pass
 that alias into a variable storing a struct, then i would want
 the copy semantics.

I think that the short of it is that what you're trying to do is impossible in D and that you'd need to use a pointer, which doesn't do quite the same thing. The long of it is 1. Don't use in. It's an alias for const scope. It won't make anything a reference. It'll just make it const, and then if scope worked properly, it would prevent the escaping of any references to that variable, which usually is not what you want (but scope is very buggy right now, so it doesn't prevent much of anything, and once it's fixed and _does_ prevent escaping, you'll get all kinds of compilations if you've been using either it or in). If you want const ref, then use const ref. 2. const ref, like ref, requires an lvalue (unlike in C++), so declaring a function which takes const ref can be very annoying. You typically need to overload it with one that takes const (but _not_ ref) and then have that one call the first one. Otherwise, it won't work with rvalues (and the overload needs to be const, or you'll end up with an infinite loop when you try and call the const ref version, because constness matches before refness when overloading). An alternative is to use auto ref (which basically declares both versions of the function for you), but then you function must be templatized, which isn't always desirable (especially if it's a member function of a class, because templated functions are never virtual). 3. If you want to return a const ref from a function, then do what you'd do in C++, but use ref const(Bar) instead of const Bar&. Note that the const uses parens. The parens are normally optional (no parens is equivalent to putting the whole type in parens), but when putting const on a member function, it's the member function which is const unless parens are used, regardless of which side of the function signature const is on. This is rather annoying (many of us would prefer that const be required to be on the right-hand side like in C++), but it's consistent with all other function attributes being able to go on either side, which is why it is the way it is (it's just that most function attributes can't affect the return type, so they don't have the same ambiguity). 4. If you want to actually save a reference to the returned variable on the stack. Tough luck. Can't do it. ref is only applicable to parameter types, return types, and foreach loop variable types. ref is _never_ used on a local variable. References in that sense don't exist in D. The closest that you'll be able to do is to return a pointer and have the caller use a pointer to the returned Bar rather than have a Bar directly on the stack. But that can work just fine, and since D doesn't have ->, the syntax is even mostly the same regardless of whether you're using Bar or Bar*. It's just that there are a few cases where you'd need to dereference (e.g. copying). 5. Returning a const Bar* could pose certain problems, because D's const is far more restrictive than C++'s const. It's transitive (so the _entire_ thing is const - const(Bar)* would be a pointer to const, wheras const(Bar *) and const Bar* are const pointers to const, but there is no way to have a const pointer to non-const; once something is const, _everything_ that it refers to is const). That can throw a big wrench in things depending on what you're doing. It's also undefined behavior to cast away const and mutate the variable (casting away const is okay, though ill-advised - it's the mutation which is undefined). So, there is no mutable keyword in D, and anything which would involve casting away const and mutating anything is verboten. So, depending on what Bar looks like, having it be const could be a big problem. If it's truly a value type, then dereferencing it and copying it to a non-const variable should work just fine, but if it's not a value type, then you're currently in trouble, because postblit constructors (which are D's version of copy constructors) don't work with const objects (only mutably ones), and that problem hasn't been solved yet (the solution will probably be to introduce actual copy constructors, but unfortunately, it hasn't been a high enough priority for it to be sorted out yet). So, there will be no standard way to copy a const object (though you could create a function which returned a mutable copy). Hopefully, that overly long post clears some stuff up for you. - Jonathan M Davis
Nov 24 2012
prev sibling next sibling parent "Stian" <stian.pedersen gmail.com> writes:
After reading that a couple of times it is a bit clearer, thank 
you. I need to wrap my head around this. I am working on a simple 
render/game engine, where, of course, vectors and matrices are 
flying around.

Is the compiler sophisticated enough that is it able to avoid the 
copying. For instance, if i have a struct Matrix4x4 a = 
node.getTransformation() and use a for whatever, but never alter 
it, will it be able to avoid the copy? What if i declare it const 
or immutable? Do i have to use a pointer to it?

After doing some small stuff in D I am not impressed by the 
mental mapping I am having with the memory system currently. I 
have some more experience with high performance programs in C++ 
and appreciate the transparency of the memory management.
Nov 24 2012
prev sibling next sibling parent "Dan" <dbdavidson yahoo.com> writes:
On Saturday, 24 November 2012 at 22:01:32 UTC, Stian wrote:
 After reading that a couple of times it is a bit clearer, thank 
 you. I need to wrap my head around this. I am working on a 
 simple render/game engine, where, of course, vectors and 
 matrices are flying around.

 Is the compiler sophisticated enough that is it able to avoid 
 the copying. For instance, if i have a struct Matrix4x4 a = 
 node.getTransformation() and use a for whatever, but never 
 alter it, will it be able to avoid the copy? What if i declare 
 it const or immutable? Do i have to use a pointer to it?

 After doing some small stuff in D I am not impressed by the 
 mental mapping I am having with the memory system currently. I 
 have some more experience with high performance programs in C++ 
 and appreciate the transparency of the memory management.

To add to Johnathan's answer, with some code. As he pointed out, you can not have: Bar nonConstBar = foo.bar; as that would be trying to make a non-const copy of something const with reference semantics. If that were allowed changes could be made to nonConstBar that would affect foo.bar which is const. If, however S had no pointers, dynamic arrays, associative arrays or members with those types, there would be no reference semantics and the copy of const into non-const would be fine (i.e. no compile error would occur). Similarly, as stated, there is no such thing as stack reference, like this in C++: S const& s = t.s; So, I think you can still have what you want and are familiar with, but you must distinguish at the point of assignment whenever crossing the boundary from non-const to const (and the reverse) for reference types. The way to do that is as you say, get a deep copy, but something special is required to do the deep copy: (https://github.com/patefacio/d-help/blob/master/d-help/opmix/mix.d) Thanks Dan --------------------- import std.stdio; import std.traits; import opmix.mix; struct Bar { private char[] c; this(this) { c = c.dup; } } class Foo { property ref const(Bar) bar() const { return _bar; } this(ref const(Bar) bar) { _bar = bar.gdup; } private Bar _bar; } void moo(ref const(Bar) bar) { writeln("foo => ", bar); } void main() { Bar bar = { ['a','b'] }; auto foo = new Foo(bar); moo(foo.bar); // Following fails because Bar has reference semantics // Compile error: conversion error from const(Bar) to Bar // Bar nonConstBar = foo.bar; // This works because gdup copies all fields recursively Bar nonConstBar = foo.bar.gdup; writeln(nonConstBar); }
Nov 24 2012
prev sibling parent "Rob T" <rob ucora.com> writes:
On Saturday, 24 November 2012 at 22:01:32 UTC, Stian wrote:
 Is the compiler sophisticated enough that is it able to avoid 
 the copying. For instance, if i have a struct Matrix4x4 a = 
 node.getTransformation() and use a for whatever, but never 
 alter it, will it be able to avoid the copy? What if i declare 
 it const or immutable? Do i have to use a pointer to it?

You may want to read through this thread to help understand how the move/copy semantics work, this part can be difficult to understand and appreciate http://forum.dlang.org/thread/cwywtkycjljdnclqtpqr forum.dlang.org
 After doing some small stuff in D I am not impressed by the 
 mental mapping I am having with the memory system currently. I 
 have some more experience with high performance programs in C++ 
 and appreciate the transparency of the memory management.

Yup, I understand that feeling fully. If you are like me, then you will be expecting D to be similar to C++ in more ways than it is, and encounters with the differences will be frustrating at first. The GC is something else to get used to, and I may never get used to it after years of mentally mapping out where and how memory is allocated and deallocated. I just don't trust that a GC will do the job properly and I have this horrible feeling that it will waste vast amounts of memory like java apps seem to do, and that it will miss deallocations for certain edge cases, like a union with a pointer or class reference in it. At this point, all I have is faith that it is doing the job properly, but that's not how I operate. I need to know exactly what it is doing and how to detect when it is not doing what I think it should be doing. This is an area I have not tackled yet but intend to. --rt
Nov 24 2012