D: C++ Streamoperator

Ich hatte gestern mal wieder mit C++ zu tun und muss sagen: ich liebe dessen Streamoperatoren. Gut bei der Ausgabe mittels cout nervt es mich schon hin und wieder, aber das einlesen von Konsoleneingaben mittels cin und vor allem bei Dateien ist es wirklich bequem und meiner Meinung nach nicht schwer zu verstehen. Vielleicht etwas kryptisch auf den ersten Blick, zugegeben, aber das legt sich bald wenn man sich erstmal daran gewöhnt hat. ;)
Für die zukünftige Version von Dgame habe ich mir die Streamoperationen von C++ emuliert, indem ich den Shift Operator überladen habe.
Damit habe ich mir eine Input/Output sowie File Stream Emulierung gebastelt:

Input & Output:

Show »

import std.stdio;
import std.conv : to;
 
immutable(string) endl = "\n\r";
 
/**
* Beispiel:
*
* ---
* Output cout = new Output();
* Input cin   = new Input();
*
* string name;
* ubyte age;
* 
* cout << "Hallo Welt!" << endl;
* cout << "Wie lautet dein Name?" << endl;
* cin >> name;
* cout << "Und wie alt bist du?" << endl;
* cin >> age;
* 
* writefln("My name is %s. I'm %d years old!", name, age);
* 
* cout << "Das ist also " << name << ". Nice to meet you." << endl;
* ---
*/
 
/**
* Autor: Randy Schütt
*/
 
class Output {
public:
	void output(T)(T param) const {
		write(param);
	}
 
	typeof(this) opBinary(string op, T)(T param) if (op == "<<") {
		this.output(param);
 
		return this;
	}
}
 
class Input {
public:
	void input(T)(ref T param) const {
		string value = readln();
 
		try {
			param = to!(T)(value[0 .. $ - 1]);
		} catch (Exception e) {
			param = cast(T)(null);
		}
	}
 
	typeof(this) opBinary(string op, T)(ref T param) if (op == ">>") {
		this.input(param);
 
		return this;
	}
}

Und File Stream:

Show »

private import std.stdio;
private import std.file;
private import std.regex;
 
/**
* Beispiel:
*
* ---
* File f = File.open("test.txt");
* 
* foreach (line; f) {
* 	  writeln(" > ", line);
* }
*
* writeln("Erste Zeile: ");
* writeln(f.readline());
* writeln("Zweite Zeile: ");
* writeln(f.readline());
*
* string third_line;
* f >>> third_line;
* writeln("Dritte Zeile: ");
* writeln(third_line);
*
* string content;
* f >> content;
*
* writeln("Content: ");
* writeln(content);
*
* f << "// Abschlusszeile";
* ---
*/
 
interface Iterator {
	void next();
	void previous();
}
 
class File : public Iterator {
private:
	const string _filename;
 
	string[] _lines;
	uint 	  _current_line;
	bool 	  _close;
 
public:
	alias readlines this;
 
	static typeof(this) open(string filename) {
		if (!File.exists(filename)) {
			throw new Exception("File not found: " ~ filename);
		}
 
		return new this(filename);
	}
 
	static bool exists(string filename) {
		return std.file.exists(filename);
	}
 
	static void put_content(string filename, string content) {
		std.file.write(filename, content);
	}
 
	static string get_content(string filename) {
		return cast(string) std.file.read(filename);
	}
 
	this(string filename) {
		if (!File.exists(filename)) {
			throw new Exception("File not found: " ~ filename);
		}
 
		this._filename = filename;
		this._current_line = 0;
 
		this.open();
		this.refresh();
	}
 
	void open() {
		this._close = false;
	}
 
	bool closed() const {
		return this._close;
	}
 
	void close() {
		this._close = true;
	}
 
	string read() const {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		return File.get_content(this._filename);
	}
 
	void write(string content) {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		std.file.append(this._filename, content ~ "\r\n");
 
		this.refresh();
	}
 
	void refresh() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this._lines = [];
 
		string line;
 
		auto f = std.stdio.File(this._filename, "r");
		while (!f.eof()) {
			line = f.readln();
 
			this._lines ~= line;
		}
		f.close();
	}
 
	const(string)[] readlines() const {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		return this._lines;
	}
 
	string readline(int line = -1) {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		if (line >= 0) {
			this._current_line = line;
		}
 
		return this._lines[this._current_line++];
	}
 
	void next() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this._current_line++;
 
		if (this._current_line >= this._lines.length) {
			this._current_line = this._lines.length;
		}
	}
 
	void previous() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		if (this._current_line > 0) {
			this._current_line--;
		}
	}
 
	uint current_line() const {
		return this._current_line;
	}
 
	string get_filename() const {
		return this._filename;
	}
 
	/*
	bool opBinary(string op, T)(T param, out int line) {
		switch (op) {
			case "in":
				foreach (string line; this) {
					auto result = std.regex.match(regex(r"" ~ param), line, "g");
 
					if (!result.empty()) {
						return true;
					}
				}
			break;
 
			default: break;
		}
 
		return false;
	}
	*/
 
	typeof(this) opBinary(string op, T)(T value) if (op == "<<") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this.write(value);
 
		return this;
	}
 
	typeof(this) opBinary(string op, T)(ref T value) if (op == ">>") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		value = this.read();
 
		return this;
	}
 
	typeof(this) opBinary(string op, T)(ref T value) if (op == ">>>") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		value = this.readline();
 
		return this;
	}
}

Vielleicht hat ja jemand Interesse und/oder Spaß daran. ;)

Referenz oder doch Zeiger?

In C oder C++ würde man stets einen Zeiger übergeben, sofern man in der zu übergebenen Methode/Funktion diesem Parameter einen neuen Wert zuweisen würde. Denn ansonsten würde dieser neu zugewiesene Wert am Ende der Methode/Funktion wieder verfallen.
Es ist nicht einmal so, dass man das tun würde, sondern das man es tun muss, da man in C/C++ keine andere Wahl hat.
In D und auch in C# hat man dafür das Keyword “ref”, wobei man in D dennoch noch Zeiger benutzen kann.
Bei einem Benchmark, welches denn nun schneller sei, stellte ich fest, dass kaum merkbare Unterschiede festzustellen sind und ref als Syntax wirklich angenehmer wirkt.
Doch wofür dann noch, außer natürlich für C Kompatibilität, in D Zeiger benutzen?
More »

D: mutable Keyword

Einige kennen sicher das Keyword mutable aus C++.
In D gibt es kein entsprechendes Äquivalent, const bleibt auch const. Diese Gebenheit nervte mich in letzter Zeit jedoch etwas. Vor allen bei debugging/Log Sachen wäre es mal nützlich, einfach eine mutable Eigenschaft in einer als const Deklarierten Methode zu verwenden.

Für alle die es nicht wissen: eine const deklarierte Methode sieht folgendermaßen aus:

  1. void foo() const {
  2. //some code here
  3. }

Das const “sagt” dem Compiler sozusagen, dass diese Methode das Objekt an sich nicht verändert.

Allerdings: wenn eine Eigenschaft eines Objekts verändert wird, ändert sich doch der komplette Zustand des Objekts. Also wozu als mutable deklarieren?
Debugging oder Logging Mitschnitte (oder derartig vergleichbares) verändern nicht wirklich den Zustand, jedenfalls nicht ausschlaggebend. Und für Debugging immer wieder das const auszukommentieren und in der Release wieder hinzuzufügen kann schon nach einer kurzen Weile etwas “nerven”.

Daher habe ich mir eine kleine Struktur geschrieben und einen dazugehörigen Test der die Benutzung demonstriert.
Damit sollte das mutable aus C++ emuliert werden. Eventuell hat ja jemand Interesse daran:

Show »

private import std.stdio;
private import std.conv;
 
final struct mutable(T) {
private:
	const T _value;
 
	void _assign(U)(U val) const {
		//writeln("private assign: " ~ to!(string)(val));
		T * copy = cast(T*) &this._value;
		*copy = cast(T) val;
	}
 
public:
	this(T val) {
		this._value = val;
	}
 
	this(const ref mutable!(T) val) {
		this._value = val.value;
	}
 
	mutable!(T) opAssign(U)(U val) const 
        if (std.traits.isIntegral!(U) 
        || std.traits.isSomeString!(U) 
        || is(U == enum)) {
		this._assign(cast(T) val);
 
		return this;
	}
 
	mutable!(T) opAssign(U)(/+const ref +/mutable!(U) val) const {
		this._assign(val.value);
 
		return this;
	}
 
	mutable!(T) opOpAssign(string op, U)(U val) const {
		switch (op) {
			case "+":
				writeln("ADD");
				U res = this._value + val;
				this._assign(res);
			break;
 
			case "-":
				writeln("SUB");
				U res = this._value - val;
				this._assign(res);
			break;
 
			case "*":
				writeln("MUL");
				U res = this._value * val;
				this._assign(res);
			break;
 
			case "/":
				writeln("DIV");
				U res = this._value / val;
				this._assign(res);
			break;
 
			default: break;
		}
 
		return this;
	}
 
	mutable!(U) opBinary(string op, U)(U val) const {
		//mutable!(U) m;
		switch (op) {
			case "+":
				writeln("Bin. ADD");
				U res = this._value + val;
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			case "-":
				writeln("Bin. SUB");
				U res = this._value - val;
 
				//m = res;+
				return mutable!(U)(res);
			//break;
 
			case "*":
				writeln("Bin. MUL");
				U res = this._value * val;
 
				//writeln(typeid(U));
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			case "/":
				writeln("Bin. DIV");
				U res = this._value / val;
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			default: break;
		}
 
		//return m;
		assert(0);
	}
 
	bool opEquals(U)(const ref mutable!(U) m) const {
		if (m is null) return false;
		if (m is this) return true;
 
		return m.value == this._value;
	}
 
	bool opEquals(U)(U m) const {
		return m == this._value;
	}
 
	@property
	T value() const {
		writeln("VALUE: ", this._value);
		return this._value;
	}
 
	string toString() const {
		return to!(string)(this._value);	
	}
}
 
enum Status {
    Stopped,
    Paused,
    Playing
}
 
class B {
private:
	mutable!(ubyte)  _id;
	mutable!(Status) _status;
 
public:			
	void pause() const {
		this._status = Status.Paused;
	}
 
	void stop() const {
		this._status = Status.Stopped;
	}
 
	void play() const {
		this._status = Status.Playing;
	}
 
	Status get_status() const {
		return this._status.value;
	}
 
	void setup(const ubyte val) const {
		_id = val;
		writefln("(=) ID: %d", _id);
 
		_id = _id.value + 2;
		writefln("(+) ID: %d", _id);
 
		_id += 2;
		writefln("(+=) ID: %d", _id);
 
		_id -= 32;
		writefln("(-=) ID: %d", _id);
 
		_id *= 2;
		writefln("(*=) ID: %d", _id);
 
		_id = _id + 2;
		writefln("(!+!) ID: %d", _id);
 
		_id = _id - 32;
		writefln("(!-!) ID: %d", _id);
 
		_id = _id * 1.2;
		writefln("(!*!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
 
		_id = _id / 2.0;
		writefln("(!/!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
	}
}
 
// in der main:
B b = new B();
b.setup(128);
 
writeln(b.get_status());
b.play();
writeln(b.get_status());
b.pause();
writeln(b.get_status());
b.play();
writeln(b.get_status());
b.stop();
writeln(b.get_status());
b.play();
writeln(b.get_status());

Nachtrag vom 12.01.2012

Ich habe die obige Struktur nochmal überarbeitet und an den Feinheiten geschliffen. Zudem habe ich nun daraus eine Klasse gemacht (geschah eher nach belieben als aus einem konkretem Grund).
Hier ist der Code:

Show »

private import std.stdio;
private import std.conv : to;
 
static private import std.traits;
 
final class Mutable(T) {
private:
	const T _value;
 
	void _assign(U)(const U val) const {
		T * copy = cast(T*) &this._value;
		*copy = cast(T) val;
	}
 
public:
	this(const T value) {
		this._value = value;
	}
 
	this(const ref T value) {
		this._value = value;
	}
 
	void set_value(U)(U val) {
		this._assign(val);
	}
 
	@property
	T value() const {
		return cast(T) this._value;
	}
 
	alias value this;
 
	@disable
	static Mutable!(T) opAssign(U)(U val) {
		return null;//new Mutable!(T)(val);
	}
 
	const(Mutable!(T)) opAssign(U)(U val) const {
	    if (is(T == Mutable)) {
            throw new Exception("Can't assign Mutable to Mutable!");
	    }
 
		this._assign(val);
 
		return this;
	}
 
	const(Mutable!(T)) opOpAssign(string op, U)(U val) const {
		switch (op) {
			case "+":
				//writeln("ADD");
				U result = this._value + val;
 
				this._assign(result);
			break;
 
			case "-":
				//writeln("SUB");
				U result = this._value - val;
 
				this._assign(result);
			break;
 
			case "*":
				//writeln("MUL");
				U result = this._value * val;
 
				this._assign(result);
			break;
 
			case "/":
				//writeln("DIV");
				U result = this._value / val;
 
				this._assign(result);
			break;
 
			default: break;
		}
 
		return this;
	}
 
	Mutable!(U) opBinary(string op, U)(U val) const {
		switch (op) {
			case "+":
				U result = this._value + val;
 
				return new Mutable!(U)(result);
 
			case "-":
				U result = this._value - val;
 
				return new Mutable!(U)(result);
 
			case "*":
				U result = this._value * val;
 
				return new Mutable!(U)(result);
 
			case "/":
				U result = this._value / val;
 
				return new Mutable!(U)(result);
 
			default: break;
		}
 
		assert(0);
	}
 
	bool opEquals(const T cmp) const {
		return cmp == this._value;
	}
 
	bool opEquals(const Mutable!(T) cmp) const {
		return cmp.value == this._value;
	}
 
	override string toString() const {
		return to!(string)(this._value);
	}
}
 
 
enum Status {
    Stopped,
    Paused,
    Playing
}
 
class B {
private:
	const Mutable!(ubyte)  _id; // mit "const" wird sichergestellt, dass _id beim ersten Aufruf initialisiert wird.
	const Mutable!(Status) _status;
 
public:
	this() {
		this._id = new Mutable!(ubyte)(0);
		this._status = new Mutable!(Status)(Status.Stopped);
	}
 
	void pause() const {
		this._status = Status.Paused;
	}
 
	void stop() const {
		this._status = Status.Stopped;
	}
 
	void play() const {
		this._status = Status.Playing;
	}
 
	Status get_status() const {
		return this._status.value;
	}
 
	void setup(const ubyte val) const {
		_id = val;
		writefln("(=) ID: %d", _id);
 
		_id = _id.value + 2;
		writefln("(+) ID: %d", _id);
 
		_id += 2;
		writefln("(+=) ID: %d", _id);
 
		_id -= 32;
		writefln("(-=) ID: %d", _id);
 
		_id *= 2;
		writefln("(*=) ID: %d", _id);
 
		_id = _id + 2;
		writefln("(!+!) ID: %d", _id);
 
		_id = _id - 32;
		writefln("(!-!) ID: %d", _id);
 
		_id = _id * 1.2;
		writefln("(!*!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
		writeln(typeid(typeof(_id)));
 
		assert(_id == 204);
 
		if (_id == 204) {
			writeln("Korrekt");
		}
 
		_id = _id / 2.0;
		writefln("(!/!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
		writeln(typeid(typeof(_id)));
 
		assert(_id == 102);
	}
}
 
 
void main() {
	B b = new B();
	b.setup(128);
 
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
	b.pause();
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
	b.stop();
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
}

D: gleichnamige Methode als Template führt zu Konflikt

Vor ein paar Tagen fiel mir ein Bug oder zumindest ein merkwürdiges Verhalten in D auf.
Jeder kennt das nette Feature, dass zwei Methoden den gleichen Namen tragen dürfen, sofern sie sich in ihren Parametern (und/oder return Typ) unterscheiden.
Außerdem ist es möglich, das eine der gleich benannten Methoden ein Template ist.
Bspw. möchte man eine Koordinaten oder Rechteck Klasse haben, in der man explizit ein short für einen x oder y Punkt zurückerwartet, aber möchte dem Benutzer das manuelle umcasten von bspw. float in short Werte abnehmen.
Man definiert also eine Template Methode, welche einen impliziten Typ deklariert den man später in der Methode zu short castet (idealerweise nur, wenn es nicht schon ein short ist ;) ).
More »

How to: Templates und Operator Overloading

In diesem Artikel möchte ich die Nützlichkeit von Templates (vor allen was Makro Anhänger in C++ angeht) und dem überschreiben von (Arithmetischen) Operatoren in C++ und D zeigen.
Ich werde ein bei mir aktuelles Bsp. aufgreifen und dann einen Lösungsweg Stück für Stück in D entwickeln, aber auch den C++ Äquivalent dazu.

Vielleicht kennt jemand das Problem schon:
Angenommen, man hat eine derartige Template Struktur

  1. alias Vector2!(float) Vector2f;
  2. alias Vector2!(short) Vector2s;
  3.  
  4. struct Vector2(T) {
  5. 	T x, y;
  6.  
  7. 	this(in T x, in T y) {
  8. 		this.x = x;
  9. 		this.y = y;
  10. 	}
  11. }

Und des Weiteren hat man ein Element wie Vector2f v1 = Vector2f(1.5, 2.3); und muss es, aus irgendeinem Grund, in einen Vector2s umwandeln/casten.
Wie stellt man das am besten an?
More »

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress