在具有不同签名的 std::function 之间进行转换(T* arg 到 void* arg)

Convert between std::function with different signatures (T* arg to void* arg)

本文关键字:arg 转换 void function std 之间      更新时间:2023-10-16

有没有办法将具有T*参数的std::function转换为具有void*参数的类似参数?这似乎是可能的,因为调用应该在二进制级别兼容。

例如,如何在不将Producer转换为模板或丢失类型安全性的情况下完成这项工作?

#include <functional>
struct Producer {
    // produces an int from a callable and an address
    template<class Src>
    Producer(Src& src, std::function<int (Src*)> f)
      : arg_(&src),
        f_(f)
    {}
    int operator()() {
        return f_(arg_);
    }
    // type erasure through void* but still type safe since ctor
    // checks that *arg_ and f are consistent
    void* arg_;
    std::function<int (void*)> f_;
};
int func1(char* c) {
    return *c;
}
int func2(int* i) {
    return *i;
}

int try_it() {
    char c = 'a';
    char i = 5;
    // we want to make these work
    Producer p1(c, func1);
    Producer p2(i, func2);
    // but we still want this to fail to compile
    // Producer p3(c, func2);
    return p1() + p2();
}

编辑:具有明确UDB但行为正确的解决方案。

你可以把这个论点当作一个std::function,然后你就不必担心了。取任何类型 F ,只要你可以用Src*调用它并且它返回可以转换为 int 的东西。从SFINAE友好的std::result_of_t开始(无耻地从Yakk借来(:

template<class F, class...Args>
using invoke_result = decltype( std::declval<F>()(std::declval<Args>()...));

并使用它来 SFINAE 你的构造函数:

template<class Src,
         class F,
         class = std::enable_if_t<
            std::is_convertible<invoke_result<F, Src*>, int>::value
         >>
Producer(Src& src, F f)
    : arg_(&src)
    , f_([f = std::move(f)](void* arg){
        return f(static_cast<Src*>(arg));  
    })
{ }

那里没有UB。这也正确地拒绝了您的p3案例。此外,除非您出于其他原因使用它,否则您甚至不需要arg_。将f_存储为:

std::function<int ()> f_;

并将Src粘在其中:

template<class Src,
         class F,
         class = std::enable_if_t<
            std::is_convertible<invoke_result<F, Src*>, int>::value
         >>
Producer(Src& src, F f)
    : f_([&src, f = std::move(f)](){
        return f(&src);  
    })
{ }