Thread overview
const after initialization / pointers, references and values
Aug 20, 2014
Vicente
Aug 20, 2014
Philippe Sigaud
Aug 20, 2014
Jakob Ovrum
Aug 21, 2014
Philippe Sigaud
Aug 21, 2014
Wyatt
Aug 22, 2014
Vicente
Aug 22, 2014
Vicente
Aug 22, 2014
Wyatt
Aug 22, 2014
Vicente
Aug 22, 2014
Vicente
August 20, 2014
Hello,
I have some questions on how to do a few things in D.
Below is an example code on which the questions are based.

I need read access to a big and complex (i.e.: nested) data
structure of type "Node".
But first it needs to be initialized. That can be done with a
default initializer or from a file.
After initialization there is no need to modify the data anymore.
My first question is: can the data be changed to "const" once
initialized?
if no, is there any alternative to acomplish that?
note that encapsulating into a class with only "get_*" methods
does not help because the "get_node" method returns a pointer to
the data
Related to that, how can the default initializer be changed to
immutable?

The second question is related to pointers, references and values
I know that structs by default use value semantics, but as data
is large I want to avoid data copying.
But I would like to avoid use of pointers, so, is there a way of
using reference semantics in the example code?
Maybe is as simple as changing from "struct Node" to "class
Node", but seems intuitive that classes carry more overhead than
structs.
How much overhead carries a class in comparison to a struct?

Regards,
   Vicente.


import std.stdio;
import std.string;

// struct or class
struct Node {
	Node[] block;
	uint num = 0;
};

// static immutable
Node[] default_nodes = [
	{num:3},
	{block:[{num:4},{num:6},{block:[{num:5},{num:7}]}]},
	// ...
];

class NodeProvider{
	// change to const after constructor
	private Node[] nodes;

	private void parse_file(char[] file_name){
		// initialize nodes from file
		for(auto i=0; i<3; i++){
			nodes.length++;
			nodes[$-1].num=i;
		}
	}

	this(){
		nodes = default_nodes;
	}
	this(char[] file_name){
		parse_file(file_name);
	}

	Node* get_node(const uint index){
		if(index>=nodes.length)
			return null;
		return &(nodes[index]);
	}
}

string NodetoString(Node n){
	string str = format("%u{ ", n.num);
	foreach(b;n.block)
		str ~= NodetoString(b);
	str ~= "} ";
	return str;
}

int main(char[][] args){
	NodeProvider np;
	Node* node_ptr;
	uint i;

	if(args.length==2)
		np = new NodeProvider(args[1]);
	else
		np = new NodeProvider();

	i = 0;
	while((node_ptr=np.get_node(i))!=null){
		writeln(NodetoString(*node_ptr));
		i++;
	}
	return 0;
}
August 20, 2014
> After initialization there is no need to modify the data anymore. My first question is: can the data be changed to "const" once initialized?

You can have const or immutable data, that can be initialized once. I tend to use pure functions to do that:


struct Node {
        Node[] block;
        uint num = 0;
}

Node makeDefaultNode() pure {
    return Node(null, 1);
}

void main(){
    immutable Node n = makeDefaultNode();

    import std.stdio;
    writeln(n);
    writeln(typeof(n).stringof);// immutable(Node)
}

You can also use module constructors:

module foo;

immutable myDefault;

static this() {
    myDefault = ...
}

/* rest of code */

I cannot comment much more. I use functions to initialize my immutable values. I don't know much about module constructors.


> Related to that, how can the default initializer be changed to immutable?

In your example code, your default value can be marked immutable, if
you want it.
Just use:

immutable Node[] default_nodes = [
        {num:3},
        {block:[{num:4},{num:6},{block:[{num:5},{num:7}]}]},
        // ...
];

> The second question is related to pointers, references and values
> I know that structs by default use value semantics, but as data
> is large I want to avoid data copying.
> But I would like to avoid use of pointers, so, is there a way of
> using reference semantics in the example code?
> Maybe is as simple as changing from "struct Node" to "class
> Node", but seems intuitive that classes carry more overhead than
> structs.

If you want reference semantics but do not want to have pointers in your code, yes classes are your best choice.


> How much overhead carries a class in comparison to a struct?

There is an overhead when calling functions, due to the virtual table. But that's mainly when you construct hierarchies of classes, so that the runtime can determine which method to call for each object. If you don't need derived classes, then I guess using a final class is your best bet. I think it discards the virtual table.

final class { ... }

I'd say: test it. Duplicate your module, change your structs to final classes and compare the speed of the two modules.
August 20, 2014
On Wednesday, 20 August 2014 at 21:26:55 UTC, Philippe Sigaud via Digitalmars-d wrote:
> If you want reference semantics but do not want to have pointers in
> your code, yes classes are your best choice.

Certainly the easiest, but I don't think it's always the best. If light-weightedness is desired, make the struct contain the reference, effectively making the struct a reference type:
---
private struct Data
{
    int a;
    string b;
    double c;
}

/// Lorem ipsum ...
struct UserFacingType
{
    private Data* data;
    // use data.foo
}
---
Of course, this means UserFacingType.init is essentially `null`, just like class references. By using std.typecons.RefCounted, the user-facing type can also easily be made a reference-counted reference.

Structs can do everything classes can with enough boilerplate. Leverage templated types like RefCounted to remove the boilerplate. Classes are just an in-built convenience feature to handle the boilerplate of virtual functions.
August 21, 2014
On Wednesday, 20 August 2014 at 21:26:55 UTC, Philippe Sigaud via Digitalmars-d wrote:
>
> If you want reference semantics but do not want to have pointers
> in your code, yes classes are your best choice.

Alternatively, isn't this a good place to use ref parameters? Or is there some semantic I'm missing here?
http://dlang.org/function.html#parameters

BTW, Vicente, the D.learn section is a great place to get help. :)

-Wyatt
August 21, 2014
On Wed, Aug 20, 2014 at 11:49 PM, Jakob Ovrum via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> Certainly the easiest, but I don't think it's always the best. If light-weightedness is desired, make the struct contain the reference, effectively making the struct a reference type

Well, yes. But the OP explicitly asked not to have to deal with pointers. So...
August 22, 2014
First of all thanks for your replies, they are useful.

@Philippe:
A pure function is ok for initializing "default_nodes" but not for "nodes" because a pure function can't read a file.
The "static this" has the problem it needs know the initializer at compile time but I wanted to choose the initializer at run time.
I know "default_nodes" can be changed to immutable, but then it will require an explicit cast to remove the immutability.
The final class may be a good point, I'll try this one.

@Jakob:
Thank you, but I'll go with Philippe's suggestion.

@Wyatt:
Certainly ref parameters help a lot, but I'm trying to get a "Node" by returning (a reference to) it. Does the ref keyword apply to the return type?
Next time I'll post to D.learn, sorry for misplacing my question.

Regards,
  Vicente.
August 22, 2014
Indeed the ref can be applied to the return type:
http://dlang.org/function.html#ref-functions

So, does the following code copy any data from "nodes"?
If that is the case this solution avoids the "class" storage, avoids pointers and "nodes" is encapsulated as read-only, that's great.
The program I'm working on is still incomplete, so I still can't benchmark it to compare with all proposed solutions.

@safe:
import std.stdio;
import std.string;

struct Node {
	Node[] block;
	uint num = 0;
};

immutable uint LAST = -1;
Node[] default_nodes = [
	{num:3},
	{block:[{num:4},{num:6},{block:[{num:5},{num:7}]}]},
	// ...
	{num:LAST},
];

class NodeProvider{
	// change to const after constructor
	private Node[] nodes;

	private void parse_file(char[] file_name){
		// initialize nodes from file
		for(auto i=0; i<3; i++){
			nodes.length++;
			nodes[$-1].num=i;
		}
		nodes.length++;
		nodes[$-1].num=LAST;
	}

	this(){
		nodes = default_nodes;
	}
	this(char[] file_name){
		parse_file(file_name);
	}

	ref const(Node) get_node(const uint index){
		return nodes[index];
	}
}

string NodetoString(ref const(Node) n){
	string str = format("%u{ ", n.num);
	foreach(b;n.block)
		str ~= NodetoString(b);
	str ~= "} ";
	return str;
}

@system: // for writeln
int main(char[][] args){
	NodeProvider np;
	uint i;

	if(args.length==2)
		np = new NodeProvider(args[1]);
	else
		np = new NodeProvider();

	for(i=0;;i++){
		const(Node) node = np.get_node(i);
		if(node.num==LAST)
			break;
		writeln(NodetoString(node));
	}
	return 0;
}
August 22, 2014
On Friday, 22 August 2014 at 18:21:06 UTC, Vicente wrote:
>
> @Wyatt:
> Certainly ref parameters help a lot, but I'm trying to get a "Node" by returning (a reference to) it. Does the ref keyword apply to the return type?

I poked it a bit and came out with this.  I _think_ it's working as expected:
import std.stdio;
import std.string;

// struct or class
struct Node {
        Node[] block;
        uint num = 0;
};

// static immutable
Node[] default_nodes = [
        {num:3},
        {block:[{num:4},{num:6},{block:[{num:5},{num:7}]}]},
        // ...
];

class NodeProvider{
        // change to const after constructor
        private Node[] nodes;

        private void parse_file(char[] file_name){
                // initialize nodes from file
                for(auto i=0; i<3; i++){
                        nodes.length++;
                        nodes[$-1].num=i;
                }
        }

        this(){
                nodes = default_nodes;
        }
        this(char[] file_name){
                parse_file(file_name);
        }

        auto ref opSlice(){return nodes[];};
}

string NodetoString(ref Node n){
        string str = format("%u{ ", n.num);
        foreach(b;n.block)
                str ~= NodetoString(b);
        str ~= "} ";
        return str;
}

int main(char[][] args){
        NodeProvider np;

        if(args.length==2)
                np = new NodeProvider(args[1]);
        else
                np = new NodeProvider();

        foreach(node_ref; np){
                writeln(NodetoString(node_ref));
        }
        return 0;
}

-Wyatt
August 22, 2014
On Friday, 22 August 2014 at 20:12:39 UTC, Wyatt wrote:
> I poked it a bit and came out with this.  I _think_ it's working as expected:
...
>         auto ref opSlice(){return nodes[];};
...
> -Wyatt

Assuming it's working as expected, that is exactly what I was looking for!
But the following code shows that at some point data gets copied. By the way, neither works what I've posted previously.
In any case, thank you very much Wyatt.

Regards,
  Vicente.

@safe:
import std.stdio;
import std.string;

struct Node { Node[] block; uint num = 0; };
/* immutable */ Node[] default_nodes = [ {num:3}, {block:[{num:4}]}, {num:6} ];

class NodeProvider{
	private /* const */ Node[] nodes;
	private Node[] tmp_nodes;

	private void parse_file(char[] file_name){
		tmp_nodes.length=1; tmp_nodes[0].num=5;
	}

	this(){ nodes = default_nodes; }
	this(char[] file_name){
		parse_file(file_name);
		nodes = tmp_nodes;
	}

	auto ref opSlice(){return nodes[];};
}

@system: // for writeln
int main(char[][] args){
	NodeProvider np;

	if(args.length==2) np = new NodeProvider(args[1]);
	else               np = new NodeProvider();

	// if they are real references this will modify the source data
	foreach(node_ref; np) node_ref.num = 101;
	// and will print all 101
	foreach(node_ref; np) writeln(node_ref.num);
	// but it prints unmodified 3, 0 and 6

	return 0;
}
August 22, 2014
If the foreach loop is replaced by:
foreach(ref node_ref; np)
Then it works like a charm!