www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Using templates with interfaces

reply Andrew Chapman <nycran gmail.com> writes:
Hi guys, I'm a little confused as to whether D supports 
interfaces with templates.  I can compile OK, but linking reports 
an error like this:

Error 42: Symbol Undefined 
_D12relationaldb10interfaces21RÇëÜDBIÇêÿ35__T7loadRowTS6prefix÷6P¶ZÇêáMFAyaAS3std7variant18Çâ└8VÇåìNVki20ZÇëÅZÇûð

Essentially my desired interface looks like this:

interface RelationalDBInterface {
     public T loadRow(T)(string sql, Variant[] params);
}

An example implementation:

public T loadRow(T)(string sql, Variant[] params)
{
	Prepared prepared = prepare(this.conn, sql);
	prepared.setArgs(params);

	auto row = prepared.queryRow();

	if (row.isNull()) {
	    throw new Exception(this.classID ~ "::loadRow - Query 
returned an empty row");
	}

	T item;
	return row.toStruct!T(item);
}

And I would try to call it like this:

auto user = this.loadRow!User(sql, params);

Is it possible, or do I need to rethink the solution?  The idea 
is to pass around a RelationalDBInterface so I can later switch 
from MySQL to Postgres or SQLite or whatever.
Jun 25
parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Sunday, 25 June 2017 at 11:39:27 UTC, Andrew Chapman wrote:
 Hi guys, I'm a little confused as to whether D supports 
 interfaces with templates.  I can compile OK, but linking 
 reports an error like this:

 Error 42: Symbol Undefined 
 _D12relationaldb10interfaces21RÇëÜDBIÇêÿ35__T7loadRowTS6prefix÷6P¶ZÇêáMFAyaAS3std7variant18Çâ└8VÇåìNVki20ZÇëÅZÇûð

 Essentially my desired interface looks like this:

 interface RelationalDBInterface {
     public T loadRow(T)(string sql, Variant[] params);
 }

 An example implementation:

 public T loadRow(T)(string sql, Variant[] params)
 {
 	Prepared prepared = prepare(this.conn, sql);
 	prepared.setArgs(params);

 	auto row = prepared.queryRow();

 	if (row.isNull()) {
 	    throw new Exception(this.classID ~ "::loadRow - Query 
 returned an empty row");
 	}

 	T item;
 	return row.toStruct!T(item);
 }

 And I would try to call it like this:

 auto user = this.loadRow!User(sql, params);

 Is it possible, or do I need to rethink the solution?  The idea 
 is to pass around a RelationalDBInterface so I can later switch 
 from MySQL to Postgres or SQLite or whatever.
You cannot have unimplemented templates in interfaces (where would they go in the virtual function table?), just return a variant. Implementations of interfaces must be classes not free functions so class MyDB : IRelationalDB { // implementation ... } which you then need to create a instance of auto mydb = MyDB(...); // connection auto user = mydb.loadRow!User(sql, params); 'this' is only valid inside an aggregate (struct or class).
Jun 25
parent reply Andrew Chapman <nycran gmail.com> writes:
On Sunday, 25 June 2017 at 13:04:32 UTC, Nicholas Wilson wrote:
 On Sunday, 25 June 2017 at 11:39:27 UTC, Andrew Chapman wrote:
 Hi guys, I'm a little confused as to whether D supports 
 interfaces with templates.  I can compile OK, but linking 
 reports an error like this:

 Error 42: Symbol Undefined 
 _D12relationaldb10interfaces21RÇëÜDBIÇêÿ35__T7loadRowTS6prefix÷6P¶ZÇêáMFAyaAS3std7variant18Çâ└8VÇåìNVki20ZÇëÅZÇûð

 Essentially my desired interface looks like this:

 interface RelationalDBInterface {
     public T loadRow(T)(string sql, Variant[] params);
 }

 An example implementation:

 public T loadRow(T)(string sql, Variant[] params)
 {
 	Prepared prepared = prepare(this.conn, sql);
 	prepared.setArgs(params);

 	auto row = prepared.queryRow();

 	if (row.isNull()) {
 	    throw new Exception(this.classID ~ "::loadRow - Query 
 returned an empty row");
 	}

 	T item;
 	return row.toStruct!T(item);
 }

 And I would try to call it like this:

 auto user = this.loadRow!User(sql, params);

 Is it possible, or do I need to rethink the solution?  The 
 idea is to pass around a RelationalDBInterface so I can later 
 switch from MySQL to Postgres or SQLite or whatever.
You cannot have unimplemented templates in interfaces (where would they go in the virtual function table?), just return a variant. Implementations of interfaces must be classes not free functions so class MyDB : IRelationalDB { // implementation ... } which you then need to create a instance of auto mydb = MyDB(...); // connection auto user = mydb.loadRow!User(sql, params); 'this' is only valid inside an aggregate (struct or class).
Sorry I wasn't very clear. My "this" was infact inside a class. Here's a more complete example of attempting to use the intertace: class MySQLRelationalDB : RelationalDBInterface { private Connection conn; private const string classID = "MySQLRelationalDB"; this(Connection conn) { this.conn = conn; } public T loadRow(T)(string sql, Variant[] params) { Prepared prepared = prepare(this.conn, sql); prepared.setArgs(params); auto row = prepared.queryRow(); if (row.isNull()) { throw new Exception(this.classID ~ "::loadRow - Query returned an empty row"); } T item; row.toStruct!T(item); return item; } } Then I use it within another class like this: class UserQuery { protected RelationalDBInterface relationalDb; this(RelationalDBInterface relationalDb) { this.relationalDb = relationalDb; } public User getUser(string emailAddress) { string sql = " SELECT * FROM usr WHERE email = ? "; auto user = this.relationalDb.loadRow!User( sql, variantArray(emailAddress) ); } } It compiles, but it wont link. Is it the case that you can't use templates with interfaces?
Jun 25
parent reply Andrew Chapman <nycran gmail.com> writes:
I think you've answered the question with "You cannot have 
unimplemented templates in interfaces".  Thanks for the answer.  
I'll rethink the way I'm doing this.

Cheers.
Jun 25
next sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Sunday, 25 June 2017 at 13:32:57 UTC, Andrew Chapman wrote:
 I think you've answered the question with "You cannot have 
 unimplemented templates in interfaces".  Thanks for the answer.
  I'll rethink the way I'm doing this.

 Cheers.
Yes, function templates in classes or interfaces are implicitly marked as final - meaning that you have to provide an implementation. However that is still useful. For example in one my projects I wanted to support configuration files written in either JSON or SDLang, however since std.json and sdlang-d had (as expected) different APIs, I decided to wrap them in classes implementing a common interface, so I could solve this in one place and be done with it. SDLang and JSON have a tree like structure and a common task is that I needed to interpret the value at a particular node as a scalar. Here's how I had done it: interface Node { final T get(T)() const { static if (isBoolean!T) return getBool(); else static if (isIntegral!T) return to!T(getInt()); else static if (isFloatingPoint!T) return to!T(getFloat()); else static if (isSomeString!T) return to!T(getString()); else static assert(0, "Type not supported: " ~ T.stringof); } const(Node) getChild(string name) const; protected: bool getBool() const; long getInt() const; double getFloat() const; string getString() const; } That way I could write a generic deserializer that iterates over the fields of the object and called the get method like this: foreach (idx, member; obj.tupleof) obj.tupleof[idx] = node.getChild( typeof(obj).tupleof[idx].stringof) .get!(typeof(member));
Jun 25
prev sibling parent reply Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Sunday, 25 June 2017 at 13:32:57 UTC, Andrew Chapman wrote:
 I think you've answered the question with "You cannot have 
 unimplemented templates in interfaces".  Thanks for the answer.
  I'll rethink the way I'm doing this.

 Cheers.
In your case you can probably use something along the lines of: interface RelationalDBInterface { // You can even make this protected Varaint loadUntypedRow(string sql, Variant[] params); final T loadRow(T)(string sql, Variant[] params) { auto row = loadUntypedRow(sql, params); enforce(row.hasValue, this.classID ~ "::loadRow - Query returned an empty row"); return row.toStruct!T; } }
Jun 25
parent Andrew Chapman <nycran gmail.com> writes:
On Sunday, 25 June 2017 at 17:30:58 UTC, Petar Kirov [ZombineDev] 
wrote:
 On Sunday, 25 June 2017 at 13:32:57 UTC, Andrew Chapman wrote:
 I think you've answered the question with "You cannot have 
 unimplemented templates in interfaces".  Thanks for the answer.
  I'll rethink the way I'm doing this.

 Cheers.
In your case you can probably use something along the lines of: interface RelationalDBInterface { // You can even make this protected Varaint loadUntypedRow(string sql, Variant[] params); final T loadRow(T)(string sql, Variant[] params) { auto row = loadUntypedRow(sql, params); enforce(row.hasValue, this.classID ~ "::loadRow - Query returned an empty row"); return row.toStruct!T; } }
Amazing, thank you!
Jun 27