How can I use opDispatch for forwarding to method with compile time parameters. See code below:
import std.stdio;
struct B{
auto p1(T)(T arg) {
writeln( "p1: ", arg );
}
auto p2(T, int C)(T s) {
writeln( "p2: ", s, " / ", C);
}
}
struct C(T) {
T b;
auto opDispatch(string s, Args...)(Args args) {
mixin("b."~s)(args);
}
}
void main() {
C!B b;
//fine: compiler is smart enough
b.p1("abc");
//oops: "no property 'p2' for type ..."
b.p2!(int, 10)(5);
B origB;
//fine:
origB.p2!(int, 10)(5);
}
EDIT
Replace class with struct: avoid usage of CTFE for field initialization with new
. It is not related to my question.
D template system is very powerful. This is more generic solution:
import std.stdio;
class B {
auto p1(T)(T arg) { writeln( "p1: ", arg ); }
auto p2(T, int C)(T s) { writeln( "p2: ", s, " / ", C); }
}
class C(T) {
T b = new T;
template opDispatch(string s) {
template opDispatch(TARGS...) {
auto opDispatch(ARGS...)(ARGS args) {
static if(TARGS.length) return mixin("b." ~ s ~ "!TARGS(args)");
else return mixin("b." ~ s ~ "(args)");
}
}
}
}
void main() {
auto b = new C!(B)();
b.p1("abc");
b.p2!(int, 10)(5);
}
http://dpaste.dzfl.pl/791c65d0e4ee
If indeed this is not possible with opDispatch
as Adam D. Ruppe's answer suggests, then your only recourse might be to turn to string mixins, which are a bit ugly but incredibly powerful. Unless using opDispatch
is a hard requirement, string mixins might be the only way.
Luckily, most of the work to do this is already done (and it's more involved than you might think). The code itself is hairy, but all you need to do is as follows:
import std.stdio;
class B{
auto p1(T)(T arg) {
writeln( "p1: ", arg );
}
auto p2(T, int C)(T s) {
writeln( "p2: ", s, " / ", C);
}
}
class C(T) {
T b = new T;
mixin(forwardToMember!(b, "p1", "p2"));
}
void main() {
auto b = new C!(B)();
b.p1("abc");
//This now compiles and correctly forwards to b.b.p2
b.p2!(int, 10)(5);
}
I've included the code below with all the unittests stripped. It's important to note that forwardToMember
currently doesn't support overloads for regular functions; it'll just pick the first instance of the specified function that it finds. I think they should work for template functions, however.
import std.traits;
import std.meta;
private alias isSomeStringType(alias str) = isSomeString!(typeof(str));
template forwardToMember(alias member, symbols...)
if (symbols.length > 0 && allSatisfy!(isSomeStringType, symbols))
{
static if (symbols.length == 1)
{
static assert(hasMember!(typeof(member), symbols[0]),
"Cannot dispatch: member '" ~ member.stringof ~
"' does not support method '" ~ symbols[0] ~ "'");
enum forwardToMember = genWrapperMixin!(member, symbols[0]);
}
else
{
enum forwardToMember = forwardToMember!(member, symbols[0]) ~ forwardToMember!(member, symbols[1..$]);
}
}
private enum SymbolKind
{
function_,
property,
templateFunction,
fieldFunction,
field,
aliasableSym,
}
//Ugly hack but there's no other way to do this
private template isTemplateFunction(f...)
if (f.length == 1)
{
import std.algorithm: among, balancedParens, canFind, count;
static if (!__traits(isTemplate, f[0]))
{
enum isTemplateFunction = false;
}
else
{
enum fstr = f[0].stringof;
//A template function's .stringof is of the format <function name>(<template args>)(<function args>)
//so match on the number of brackets to determine whether it's a template function or not
enum isTemplateFunction = __traits(isTemplate, f)
&& fstr.balancedParens('(', ')')
&& (fstr.canFind("if")
|| fstr.count!(c => cast(bool)c.among!('(', ')')) == 4);
}
}
private template getSymbolKind(Aggregate, string symbol)
{
import std.traits;
import std.typetuple;
enum getMemberMixin = "Aggregate." ~ symbol;
//Appears in Aggregate.tupleof so it must be a field
static if (staticIndexOf!(symbol, FieldNameTuple!Aggregate) > -1)
{
//Check if it's a regular field or a function pointer
static if (isSomeFunction!(mixin(getMemberMixin)))
enum getSymbolKind = SymbolKind.fieldFunction;
else
enum getSymbolKind = SymbolKind.field;
}
else
{
static if (isSomeFunction!(mixin(getMemberMixin))
&& !__traits(isStaticFunction, mixin(getMemberMixin))
|| isTemplateFunction!(mixin(getMemberMixin)))
{
static if (isTemplateFunction!(mixin(getMemberMixin)))
enum getSymbolKind = SymbolKind.templateFunction;
else static if (functionAttributes!(mixin(getMemberMixin)) & FunctionAttribute.property)
enum getSymbolKind = SymbolKind.property;
else
enum getSymbolKind = SymbolKind.function_;
}
//If it's not a member function/property then it should be an aliasable static symbol
else static if (__traits(compiles, { alias _ = Alias!(mixin(getMemberMixin)); }))
enum getSymbolKind = SymbolKind.aliasableSym;
else
static assert(0, "Error: " ~ Aggregate.stringof ~ "." ~ symbol ~ " is not a member function, field, or aliasable symbol");
}
}
private template genWrapperMixin(alias member, string symbol)
{
import std.algorithm: among;
import std.string: format;
enum symbolKind = getSymbolKind!(typeof(member), symbol);
static if (symbolKind.among!(SymbolKind.function_, SymbolKind.property, SymbolKind.fieldFunction))
{
alias MethodType = FunctionTypeOf!(mixin("member." ~ symbol));
enum funAttrs = functionAttributes!MethodType;
enum methodIsStatic = __traits(isStaticFunction, mixin("member." ~ symbol));
enum funAttrStr = getFunctionAttributeStr(funAttrs) ~ (methodIsStatic ? " static" : "");
//Workaround Issue 14913
enum returnStr = funAttrs & FunctionAttribute.return_ ? "return" : "";
enum genWrapperMixin = q{
%3$s
auto ref %2$s(ParameterTypeTuple!(FunctionTypeOf!(%1$s.%2$s)) args) %4$s
{
import std.functional: forward;
return %1$s.%2$s(forward!args);
}
}
.format(member.stringof, symbol, funAttrStr, returnStr);
}
else static if (symbolKind == SymbolKind.templateFunction)
{
enum genWrapperMixin = q{
template %2$s(TemplateArgs...)
{
auto ref %2$s(FunArgs...)(auto ref FunArgs args)
{
import std.functional: forward;
return %1$s.%2$s!(TemplateArgs)(forward!args);
}
}
}
.format(member.stringof, symbol);
}
else static if (symbolKind == SymbolKind.field)
{
alias FieldType = typeof(mixin("member." ~ symbol));
alias FA = FunctionAttribute;
enum attrStr = getFunctionAttributeStr(FA.pure_ | FA.nothrow_ | FA.safe | FA.nogc);
enum genWrapperMixin = q{
@property %3$s %4$s %1$s()
{
return %2$s.%1$s;
}
@property %3$s void %1$s(%4$s val)
{
%2$s.%1$s = val;
}
}
.format(symbol, member.stringof, attrStr, FieldType.stringof);
}
else static if (symbolKind == SymbolKind.aliasableSym)
{
enum genWrapperMixin = q{
alias %1$s = %2$s.%1$s;
}
.format(symbol, member.stringof);
}
else
static assert(member.stringof ~ "." ~ symbol ~ " has unexpected kind '" ~ symbolKind.to!string);
}
private string getFunctionAttributeStr(FunctionAttribute funAttrs)
{
import std.algorithm: among, filter, joiner, map, strip;
import std.conv: to;
string funAttrStr;
with (FunctionAttribute)
{
funAttrStr = [EnumMembers!FunctionAttribute]
.filter!(e => (funAttrs & e) && e != none && e != ref_ && e != return_)
.map!(e => e.to!string.strip('_'))
.map!(s => s.among!("safe", "trusted", "system", "nogc", "property") ? '@' ~ s : s)
.joiner(" ")
.to!string;
}
return funAttrStr;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With