www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - My design need friends

reply "Namespace" <rswhite4 googlemail.com> writes:
Hello. I hope one of you has a good idea to solve my design 
problem.
I have 3 files in two different sub packages.

Package Bar has the interface Drawable:
----
module Bar.Drawable;

interface Drawable {
protected:
	void _render();

package:
	final void render() {
		this._render();
	}
}
----

and, for example, the class Graphic:
----
module Bar.Graphic;

import std.stdio;

import Bar.Drawable;

class Graphic : Drawable {
protected:
	override void _render() const {
		writeln("Graphics.render");
	}
}
----

Package Foo has the class Window:
----
module Foo.Window;

import std.stdio;

import Bar.Drawable;

class Window {
public:
	void draw(Drawable d) {
		writeln("Window.draw");

		d.render(); /// Error: interface Bar.Drawable.Drawable member 
render is not accessible

	}
}
----

As you can see, I try to access the render method from drawable. 
But since Drawable is in another sub package, I have no access.
That is a situation where I liked to have 'friends' like in C++.

I have two solutions:
  1. Move Window from package Foo to the package Bar. But that's a 
ugly solution and I don't want to have any Window components in 
my Bar package.
  2. Make 'render' public instead package. That is my current 
workaround. But I don't like it much since each other could also 
access 'render', not only Window.

My question is: is there any other possible solutions? Any D 
magic?

Thanks in advance.
Oct 05 2013
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
It isn't really a fix, but in these situations what I've been 
doing in my code is just writing:

// don't use this
/* private */ final public void foo() {}

final makes sure it doesn't get overridden wrongly, and then the 
comments make my intention a little more clear.
Oct 05 2013
next sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
On Saturday, 5 October 2013 at 21:46:19 UTC, Adam D. Ruppe wrote:
 It isn't really a fix, but in these situations what I've been 
 doing in my code is just writing:

 // don't use this
 /* private */ final public void foo() {}

 final makes sure it doesn't get overridden wrongly, and then 
 the comments make my intention a little more clear.
Yeah, or I let the render method uncommented, so that nobody can see it in the resulting DDoc. :D But this is no real solution. Maybe we should convince Walter for something like 'friend'? I know that we have something similar with classes in the same module, but that is ugly and unuseable in my situation.
Oct 05 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Saturday, 5 October 2013 at 21:57:58 UTC, Namespace wrote:
 On Saturday, 5 October 2013 at 21:46:19 UTC, Adam D. Ruppe 
 wrote:
 It isn't really a fix, but in these situations what I've been 
 doing in my code is just writing:

 // don't use this
 /* private */ final public void foo() {}

 final makes sure it doesn't get overridden wrongly, and then 
 the comments make my intention a little more clear.
Yeah, or I let the render method uncommented, so that nobody can see it in the resulting DDoc. :D But this is no real solution. Maybe we should convince Walter for something like 'friend'? I know that we have something similar with classes in the same module, but that is ugly and unuseable in my situation.
Something that would expand the package modifier, so that it belongs to the main package and not to the subpackage, would also fulfill the purpose. Something like 'internal'.
Oct 05 2013
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 10/5/13, Adam D. Ruppe <destructionator gmail.com> wrote:
 It isn't really a fix, but in these situations what I've been
 doing in my code is just writing:

 // don't use this
 /* private */ final public void foo() {}
Me too, as have other library writers (like DFL).
Oct 05 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
I have now a solution. A bit dirty but it's the D magic I 
expected.

New file:
----
module Core.Friend;

struct Friend {
public:
	immutable string friend;
}
----

Drawable looks now like this:
----
module Bar.Drawable;

import Core.Friend;

 Friend("Window") interface Drawable {
protected:
	void _render();

package:
	final void render() {
		this._render();
	}
}
----

And window looks like this:
----
class Window {
public:
	void draw(Drawable d) {
		writeln("Window.draw");

		//d.render(); /// Error: interface Bar.Drawable.Drawable member 
render is not accessible
		Accessor.friendCall!(Window, "render")(d);
	}
}
----

As you can see, we have now an 'Accessor':
----
module Bar.Accessor;

import std.stdio;
import std.string : format;

import Core.Friend;

abstract final class Accessor {
public:
	static void friendCall(Request, string method, T, Args...)(ref T 
obj, Args args) {
		auto friend = __traits(getAttributes, T);

		static if (friend.length != 0 && is(typeof(friend[0]) == 
Friend)) {
			if (friend[0].friend == __traits(identifier, Request)) {
				mixin("obj." ~ method ~ "(args);");
			} else {
				throw new Exception(format("%s is not a friend of %s.",
										   __traits(identifier, Request), __traits(identifier, 
T)));
			}
		} else {
			throw new Exception(format("%s has no friends.",
									   __traits(identifier, T)));
		}
	}
}
----

Any further suggestions or improvements?
----
Oct 05 2013
next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
Even better as mixin template:

Accessor:
----
module Core.Accessor;

import std.stdio;
import std.string : format;

import Core.Friend;

mixin template Accessor(T) {
public:
	void friendCall(string method, Request, Args...)(ref const 
Request caller, Args args) {
		auto friends = __traits(getAttributes, T);

		static if (friends.length != 0 && is(typeof(friends[0]) == 
Friend)) {
			foreach (ref const Friend friend; friends) {
				if (friend.friend == __traits(identifier, Request)) {
					mixin("return this." ~ method ~ "(args);");
				}
			}

			throw new Exception(format("%s is not a friend of %s.",
									   __traits(identifier, Request),
									   __traits(identifier, T)));
		} else {
			throw new Exception(format("%s has no friends.",
									   __traits(identifier, T)));
		}
	}
}
----

Drawable:
----
 Friend("Window") interface Drawable {
protected:
	void _render();

package:
	final void render() {
		this._render();
	}

public:
	mixin Accessor!Drawable;
}
----

And Window:
----
class Window {
public:
	void draw(Drawable d) {
		writeln("Window.draw");

		//d.render(); /// Error: interface Bar.Drawable.Drawable member 
render is not accessible
		d.friendCall!("render")(this);
	}
}
----

Perhaps something should be added to std.typecons.
Oct 05 2013
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 10/6/13, Namespace <rswhite4 googlemail.com> wrote:
 Any further suggestions or improvements?
That's nice. You could also hack away with templates, extracting the full module path (e.g. foo.bar) of the calling module and comparing this with the module a symbol is in, and then do this sort of thing: In a module like foolib.mod: - If some API-marked function is called from any submodule of "foolib" => compilation proceeds - Otherwise, statically assert. However I've just realized we are missing a __MODULE__ equivalent which gives us the full path of the module. For example, for a.b.c returns c, rather than a.b.c. Adam D. Ruppe will probably understand where I'm going with this. Just to pseudocode a bit: test.d: ----- module test; import foo.a; import foo.b; void main() { B b; b.func(); // this will fail } ----- foo/a.d: ----- module foo.a; import foo.b; void test() { B b; b.func(); // ok, compiles } ----- foo/b.d: ----- module foo.b; import foo.internal; struct B { // introduce public function "func" only accessible from any 'foo' submodules // which forwards to "funcImpl" mixin Internal!(funcImpl, "func"); private void funcImpl() { } // internal } ----- foo/internal.d: ----- module foo.internal; mixin template Internal(alias symbol, string funcIdent) { mixin(" public auto " ~ funcIdent ~ "(string packName = __PACKAGE__, T...)(T t) { // note: not the actual assert, you would probably do string comparisons here, // e.g. checking whether both packages start with "foo." static assert(__PACKAGE__ == packName); return symbol(t); } "); } ----- The only issue is there's no way to get to the caller's fully qualified module name. You could hack around extracting this info from a __PRETTY_FUNCTION__ string, but it's unreliable. I think we may need a new __PACKAGE__ symbol.
Oct 05 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
This is also nice.
My final construct now looks like this:

Friend:
----
module Core.Friend;

struct Friend {
public:
	immutable string FriendClass;
	immutable string FriendMethod;

	this(string friendClass, string friendMethod = null) {
		this.FriendClass = friendClass;
		this.FriendMethod = friendMethod;
	}
}
----

Accessor:
----
module Core.Accessor;

import std.stdio;
import std.string : format;

import Core.Friend;

class FriendException : Exception {
public:
	this(string msg, string file = __FILE__, size_t line = __LINE__, 
Throwable next = null) {
		super(msg, file, line, next);
	}
}

mixin template Accessor(T) {
public:
	void friendCall(string method, Request, Args...)(ref const 
Request caller, Args args) {
		auto friends = __traits(getAttributes, T);

		immutable string ReqStr = __traits(identifier, Request);
		immutable string Tstr = __traits(identifier, T);

		static if (friends.length != 0 && is(typeof(friends[0]) == 
Friend)) {
			foreach (ref const Friend friend; friends) {
				if (friend.FriendClass == ReqStr) {
					if (friend.FriendMethod.length != 0 && method != 
friend.FriendMethod) {
						throw new FriendException(format("%s is a friend of %s but 
no friend of method %s.%s.",
														ReqStr, Tstr, Tstr, method));
					}

					mixin("return this." ~ method ~ "(args);");
				}
			}

			throw new FriendException(format("%s is not a friend of %s.", 
ReqStr, Tstr));
		} else {
			throw new FriendException(format("%s has no friends.", Tstr));
		}
	}
}
----

And Drawable:
----
 Friend("Window", "render") interface Drawable {
protected:
	void _render();

package:
	final void render() {
		this._render();
	}

public:
	mixin Accessor!Drawable;
}
----

I'm in favor that something like that is really added to 
std.typecons.
And I should write a blog post about your and my solution. :)
Oct 06 2013
parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 10/6/13, Namespace <rswhite4 googlemail.com> wrote:
 And I should write a blog post about your and my solution. :)
Let me try to hack on __PRETTY_FUNCTION__ first and I'll post a working example here soon.
Oct 06 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
On Sunday, 6 October 2013 at 13:11:02 UTC, Andrej Mitrovic wrote:
 On 10/6/13, Namespace <rswhite4 googlemail.com> wrote:
 And I should write a blog post about your and my solution. :)
Let me try to hack on __PRETTY_FUNCTION__ first and I'll post a working example here soon.
Is there something new?
Oct 11 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Friday, 11 October 2013 at 22:39:18 UTC, Namespace wrote:
 On Sunday, 6 October 2013 at 13:11:02 UTC, Andrej Mitrovic 
 wrote:
 On 10/6/13, Namespace <rswhite4 googlemail.com> wrote:
 And I should write a blog post about your and my solution. :)
Let me try to hack on __PRETTY_FUNCTION__ first and I'll post a working example here soon.
Is there something new?
I have. Foo_1: ---- import std.stdio; import Bar_1; struct friend(T) { public: alias Friend = T; } friend!(Bar) class Foo { public: void call(Bar b) { _call_a_friend(b, this, &b.bar, 42); //b.bar(42); } } ---- Bar_1: ---- import std.stdio; import std.string : format; class Bar { package: void bar(int id) { writeln("Bar.bar with ", id); } } void _call_a_friend(F, T, string filename = __FILE__, size_t line = __LINE__, Args...)(const F friend, const T caller, void delegate(Args) dg, Args args) { bool isFriend = false; foreach (attr; __traits(getAttributes, T)) { isFriend = is(attr.Friend) && is(attr.Friend == F); if (isFriend) break; } //writeln(isFriend); if (isFriend) dg(args); else throw new Exception(format("%s is not a friend of %s.", __traits(identifier, T), __traits(identifier, F)), filename, line); } ---- main: ---- import Foo_1; import Bar_1; void main() { Foo f = new Foo(); Bar b = new Bar(); f.call(b); } ---- I have to admit that the '_call_a_friend' method is a bit parameter heavy. :D Suggestions?
Oct 11 2013