Using templates with interfaces

Andrew Chapman via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Jun 25 06:29:19 PDT 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:
>> 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?


More information about the Digitalmars-d-learn mailing list