0

I'm working with a non-modifiable interface that looks something like this:

struct Foo {
    static Foo* get() { static Foo foo; return &foo; }
    int (*func_ptr)(float, double);
    int func_impl(float x, double y) { return 0; }
};

I'd like to bind func_ptr so that it invokes func_impl on the static get() instance. Currently, I'm doing the following:

Foo::get()->func_ptr = [](float x, double y) -> int {
    return Foo::get()->func_impl(x, y);
};

But I'd like this to be more concise. In particular, I'm trying to avoid duplicating the parameter types and names. After reviewing several related questions, I'm convinced that there's no way to generically assign the member function to the function pointer, but I think it still should be possible to assign it a specific implementation that happens to use Foo::get using templates. Is this accurate? If so, how can I define such a binder function? I've tried things like the following, but I've hit a wall in defining the binder, and I'm not fluent in clang-template-error.

template<typename TFuncPtr, typename TFuncImpl, typename... TArgs>
void BindImplToPtr(TFuncPtr ptr, TFuncImpl impl) {
    Foo::get()->(*ptr) = [](TArgs... args) -> int {
        return Foo::get()->(*impl)(args...);
    };
}

The goal would be to be able to bind the pointer and implementation using a single call:

BindImplToPtr(&Foo::func_ptr, &Foo::func_impl);
MooseBoys
  • 6,641
  • 1
  • 19
  • 43

2 Answers2

1
    Foo::get()->func_ptr = [](auto ...x) {
      return Foo::get()->func_impl(x...);
    };

this lift works, but any implementation whose parameters implicitly convert from desired signature, can bind. Exact signature matching would require exotic meta-programming. If you are as sharp as boost or std implementors, you can go for that too.

Red.Wave
  • 2,790
  • 11
  • 17
1

There are a few steps to get there:

  1. The arguments you pass are pointer-to-members, which have a different syntax to use:

    int(T::*pm) = &T::x;
    int(T::*pmf)(int) = &T::foo;
    
    T obj;
    int i1 = obj.*pm; // Or std::invoke(pm, obj)
    int i2 = (obj.*pmf)(0); // Or std::invoke(pmf, obj, 0)
    

    Changing this, BindImplToPtr becomes:

    Foo::get()->*ptr = [](TArgs... args) -> int {
        return (Foo::get()->*impl)(args...);
    };
    
  2. impl needs to be captured, but that disallows conversion to a function pointer (because function pointers can't store your extra state). It seems likely that you know these at compile-time, so you can make impl a template parameter instead, and you might as well make ptr one too at that point:

    template<typename... TArgs, auto Ptr, auto Impl>
    void BindImplToPtr() {
        Foo::get()->*Ptr = [](TArgs... args) -> int {
            return (Foo::get()->*Impl)(args...);
        };
    }
    
  3. The other answer can replace steps 3 and 4, but I'll go for a different approach. It would be user-friendly to show up front that the function pointer and impl need to have matching signatures. You can do this directly or add some aliases to make it slightly more readable:

    template<typename... TArgs>
    using FooSig = int(TArgs...);
    
    template<typename... TArgs>
    using FooPtr = FooSig<TArgs...>*(Foo::*);
    
    template<typename... TArgs>
    using FooImpl = FooSig<TArgs...>(Foo::*);     
    
    template<typename... TArgs, FooPtr<TArgs...> Ptr, FooImpl<TArgs...> Impl>
    void BindImplToPtr() {
        Foo::get()->*Ptr = [](TArgs... args) -> int {
            return (Foo::get()->*Impl)(args...);
        };
    }
    
  4. You want to deduce TArgs based on Ptr and Impl. The other answer shows how to sidestep this with a form of contravariance, which is shorter and doesn't affect the users of Foo due to the contravariance existing only during the binding, but loses the ability of the above error improvements for mismatching ptr-impl pairs. I'll show how to do it the old-fashioned way.

    Normally, you'd use an overload for partial specialization on function templates, but it's not as straightforward as that in this case. An easy way out is to go through a class:

    namespace detail {
        template<auto Ptr, auto Impl>
        struct BindImplToPtr {
            static_assert(false, "Bad ptr and impl. ptr must be a function pointer with the same signature as impl.");
        };
    
        template<typename... TArgs, FooPtr<TArgs...> Ptr, FooImpl<TArgs...> Impl>
        struct BindImplToPtr<Ptr, Impl> {
            static void Run() {
                Foo::get()->*Ptr = [](TArgs... args) -> int {
                    return (Foo::get()->*Impl)(args...);
                };
            }
        };
    }
    
    template<auto Ptr, auto Impl>
    void BindImplToPtr() {
        detail::BindImplToPtr<Ptr, Impl>::Run();
    }
    

    This degrades the error, so I added a static assertion to bring the quality back up, and the expected pattern is just below that in the code. Ideally, I'd probably go with the other answer and use auto plus a constraint/assertion based on a function_traits implementation to keep the code a good mix of simpler and user-friendly, but that's getting out of scope.

You can find a complete example here.

chris
  • 60,560
  • 13
  • 143
  • 205