Thread overview
How to write a counterpart to C++ std::invoke that works with both free functions and methods?
Sep 26
60rntogo
Sep 27
60rntogo
Oct 04
60rntogo
September 26
I tried this:

---
auto invoke(alias fun, Args...)(Args args)
{
  return fun(args);
}
---

which works with free functions:

---
int add(int a, int b)
{
  return a + b;
}

assert(invoke!add(1, 2) == 3);
---

but with a method:

---
struct Foo
{
  bool isValid(int a)
  {
    return a > 0;
  }
}

auto foo = Foo();
assert(invoke!isValid(foo, 3));
---

I get the error "undefined identifier isValid". How can I make this work?
September 26
On Saturday, 26 September 2020 at 22:58:44 UTC, 60rntogo wrote:
> I get the error "undefined identifier isValid". How can I make this work?

This part is easy, you need to give the name like

assert(invoke!(Foo.isValid)(foo, 3));


Now, the other part is tricky, and a new feature just released this week is there to help:

https://dlang.org/changelog/2.094.0.html#add_traits_child

So you use this to branch on static vs non-static functions:

auto invoke(alias fun, Args...)(Args args)
{
  static if(__traits(isStaticFunction, fun))
    return fun(args);
  else
    return __traits(child, args[0], fun)(args[1 .. $]);
}


So, for static functions, you won't be passing a `this`, so it just sends the args straight in.

But for non-static functions, you use the now trait to attach `this` to the given function, then pass the rest of the arguments normally.

As a result, you `add` works fine, and `assert(invoke!(Foo.isValid)(foo, 3));` now works too!
September 27
On Saturday, 26 September 2020 at 23:23:13 UTC, Adam D. Ruppe wrote:
> As a result, you `add` works fine

I'm afraid not. __traits(isStaticFunction, add) is false, I think it's because it checks if it is a static member function of some struct/class. How would I check if it is actually a free function? I tried

isAggregateType!(__traits(parent, add))

but this doesn't even compile since I defined add inside my main function and __traits(parent, add) evaluates to main, but isAggregateType expects a type as an argument.
September 29
On Sunday, 27 September 2020 at 05:22:36 UTC, 60rntogo wrote:
> How would I check if it is actually a free function?
>
> but this doesn't even compile since I defined add inside my main function

aaaaah that's not a free function!!

That's a nested function and thus actually has a hidden argument. Of course, you could add `static` to it if it doesn't use other local variables, then it will work again.

But to handle nested functions, you can simply add a check for isNested too.


Bringing me to this:

-----
auto invoke(alias fun, Args...)(Args args)
{
  static if(__traits(isStaticFunction, fun) || __traits(isNested, fun))
    return fun(args);
  else
    return __traits(child, args[0], fun)(args[1 .. $]);
}

// I think the above covers all the cases, what follows
// are just some tests of various situations.

struct Foo
{
  bool isValid(int a)
  {
    return a > 0;
  }

  struct Bar {
        void bar() {}
}

Bar bar;
}

int add3(int a, int b)
{
  return a + b;
}

class A {
        void test() {}

        int item;

        class B {
                void other(int arg) { import std.stdio; writeln(item, " nested ", arg); }
        }

        static class C {
                void cool(string) {}
        }
}

void main() {

        auto a = new A;
        a.item = 30;
        invoke!(A.C.cool)(new A.C, "ok");
        invoke!(A.B.other)(a.new B, 6);
        invoke!(A.test)(a);
        invoke!(Foo.Bar.bar)(Foo.Bar.init);

        int add(int a, int b)
        {
          return a + b;
        }
        int add2(int a, int b)
        {
          return a + b;
        }


        assert(invoke!add(1, 2) == 3);
        assert(invoke!add2(1, 2) == 3);
        assert(invoke!add3(1, 2) == 3);

        auto foo = Foo();
        assert(invoke!(Foo.isValid)(foo, 3));
}
-------
October 04
On Tuesday, 29 September 2020 at 01:19:48 UTC, Adam D. Ruppe wrote:
> On Sunday, 27 September 2020 at 05:22:36 UTC, 60rntogo wrote:
>> How would I check if it is actually a free function?
>>
>> but this doesn't even compile since I defined add inside my main function
>
> aaaaah that's not a free function!!

OK, I see. That makes sense I suppose. Thanks for putting this together, but I'm afraid that I managed to break it again and this time it is really baffling.

---
module foo;

auto invoke(alias fun, Args...)(Args args)
{
  static if(__traits(isStaticFunction, fun) || __traits(isNested, fun))
    return fun(args);
  else
    return __traits(child, args[0], fun)(args[1 .. $]);
}

unittest
{
  import std : approxEqual;
  assert(invoke!approxEqual(2.0, 2.001));
}
---

This gives me these errors:

---
source/foo.d(6,48): Error: aggregate or function expected instead of approxEqual(T, U, V)(T value, U reference, V maxRelDiff = 0.01, V maxAbsDiff = 1e-05)
source/foo.d(9,41): Error: template std.math.approxEqual cannot deduce function from argument types !()(double), candidates are:
/usr/include/dmd/phobos/std/math.d(8479,6):        approxEqual(T, U, V)(T value, U reference, V maxRelDiff = 0.01, V maxAbsDiff = 1e-05)
source/foo.d(15,28): Error: template instance foo.invoke!(approxEqual, double, double) error instantiating
---

Confusingly, these errors disappear if I include the unit test in my main file (i.e., just add it to the code you posted) rather than foo.d or if I use a function like add defined in the module instead of imported approxEqual. Any ideas?