digitalmars.D.learn - Using templates with interfaces
- Andrew Chapman (27/27) Jun 25 2017 Hi guys, I'm a little confused as to whether D supports
- Nicholas Wilson (14/41) Jun 25 2017 You cannot have unimplemented templates in interfaces (where
- Andrew Chapman (44/96) Jun 25 2017 Sorry I wasn't very clear. My "this" was infact inside a class.
- Andrew Chapman (4/4) Jun 25 2017 I think you've answered the question with "You cannot have
- Petar Kirov [ZombineDev] (37/41) Jun 25 2017 Yes, function templates in classes or interfaces are implicitly
- Petar Kirov [ZombineDev] (16/20) Jun 25 2017 In your case you can probably use something along the lines of:
- Andrew Chapman (3/24) Jun 27 2017 Amazing, thank you!
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 2017
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 2017
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: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?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 2017
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 2017
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 2017
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 2017
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:Amazing, thank you!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 27 2017