1

I'm working with webview (found here: https://github.com/webview/webview) to create a c++ program with a html/js gui. In order to call c++ functions from js, they must be in the form std::string function(std::string). This is fairly trivial for free functions, however it seems not so trivial if you want to pass a pointer to a member function.

So, I've written a class which stores a reference to the object and its function, and in the constructor webview::bind is called with lambda function which parses the string input, and calls the function. It then converts the result to a string (I'm assuming this will be used for cases where there is a function that can do this) and returns that.

Now strangely this seems to work for a member function with no parameters (E.g. testcase::num below) but if I include one with parameters I get the error:

testcase.cpp:14:22: error: no matching function for call to 'invoke'
            auto r = std::invoke(f, o, std::forward<Args>(get<Args>(args))...);
                     ^~~~~~~~~~~
testcase.cpp:55:15: note: in instantiation of member function 'binder<testclass, int (testclass::*)(int, int)>::binder' requested here
    auto b2 = binder(w, tc, &testclass::add, "add");
              ^
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/functional:2845:1: note: candidate template ignored: substitution failure [with _Fn = int
      (testclass::*&)(int, int), _Args = <testclass &>]: no type named 'type' in 'std::__1::invoke_result<int (testclass::*&)(int, int), testclass &>'
invoke(_Fn&& __f, _Args&&... __args)
^

Below the example code, which is compiled on a Mac with g++ testcase.cpp -o testcase -std=c++17 -framework WebKit:

#include <string>
#include <sstream>
#include <functional>

#include <iostream>
#include "webview.h"

template<class Obj, class F, class ...Args>
class binder{
public:
    binder(webview::webview &w, Obj& obj, F func, std::string name) : _w(w), o(obj), f(func) {
        _w.bind(name, [&](std::string s)->std::string {
            std::stringstream args(s);
            auto r = std::invoke(f, o, std::forward<Args>(get<Args>(args))...);
            return std::to_string(r);
        });
    }

private:
    template<class T>
    static T get(std::istream& args){
        T t; // must be default constructible
        if(!(args >> t)){
            args.clear();
            throw std::invalid_argument("invalid argument to stream_function");
        }
        return t;
    }

    Obj& o;
    F f;

    webview::webview& _w;
};

class testclass {
public:
    int add (int a, int b) {
        return a+b;
    }
    int num () {
        return 5;
    }
};

int main() {

    webview::webview w(true, nullptr);
    w.set_title("test");
    w.set_size(1200, 800, WEBVIEW_HINT_NONE);


    testclass tc;
    auto b1 = binder(w, tc, &testclass::num, "num"); // This compiles
    auto b2 = binder(w, tc, &testclass::add, "add"); // This doesn't 

    w.navigate(R"(data:text/html,
        <!doctype html>
        <html>
          <body>
          <div id='num'></div>
          <div id='add'></div>
          </body>
          <script>
            window.onload = function() {
              num('hello').then(function(res) {
                document.getElementById('num').innerHTML = res;
                console.log('num res', res);
              });
              add(1, 2).then(function(res) {
                document.getElementById('add').innerHTML = res;
                console.log('add res', res);
              });
            };
          </script>
        </html>
      )");
    w.run();
}

Thanks for your help

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Naitsirk
  • 13
  • 3
  • Where are the two `int` arguments coming from? Perhaps you need to specify the template arguments `binder` – Artyer Aug 28 '20 at 12:56
  • Hmm, I'd assumed that they could be inferred, given the rest of the function signature could be, but looks like they can't. Any idea why that is? Either way it works now with it! – Naitsirk Aug 28 '20 at 14:10

1 Answers1

1

With

auto b1 = binder(w, tc, &testclass::num, "num"); // This compiles
auto b2 = binder(w, tc, &testclass::add, "add"); // This doesn't

You use CTAD (C++17), but from:

template <class Obj, class F, class ...Args>
class binder{
public:
    binder(webview::webview &w, Obj& obj, F func, std::string name);
    // ...
};

Args... cannot be deduced and is so an empty pack.

You might add your deduction guides to solve that:

template <class Obj, class Ret, class ...Args>
binder(webview::webview &, Obj&, Ret (Obj::*) (Args...) const, std::string) -> binder<Obj, Ret (Obj::*) (Args...) const, Args...>;

template <class Obj, class Ret, class ...Args>
binder(webview::webview &, Obj&, Ret (Obj::*) (Args...), std::string) -> binder<Obj, Ret (Obj::*) (Args...), Args...>;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Nice answer, but I think you just forgot the `Obj& obj` parameter in the deduction guides. – nop666 Aug 31 '20 at 07:25
  • Indeed, "typo" fixed. – Jarod42 Aug 31 '20 at 17:39
  • Ah, this is exactly what I was looking for! I'd also managed to get it to work by adding a class which contains all the bindings. It seems by having this extra layer, it could deduce the types - but this is a much simpler method. Thanks! – Naitsirk Sep 07 '20 at 15:52