Thread overview
How to write a counterpart to C++ std::invoke that works with both free functions and methods?
Sep 26, 2020
60rntogo
Sep 26, 2020
Adam D. Ruppe
Sep 27, 2020
60rntogo
Sep 29, 2020
Adam D. Ruppe
Oct 04, 2020
60rntogo
September 26, 2020
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, 2020
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, 2020
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, 2020
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, 2020
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?