将std :: function转换为另一个std :: function并调用它?

时间:2017-02-19 22:39:59

标签: c++ function c++11 casting arguments

我有一个问题,将一种类型的std :: function转换为另一种类型的参数或更多,然后调用它,因为它适用于所有编译器,但我不确定它是否是一个已定义的行为。

std::function<void(int, float)> func1 = [](int a, float b){
  std::cout << a << std::endl;
  std::cout << b << std::endl;
};

std::function<void(int, float, int, double)>& func2 =
*reinterpret_cast<std::function<void(int, float, int, double)>*>(&func1);

func2(1, 2.0f, 3, 4.0);

这似乎可以用预期的args 1,2.0f正确调用func1。其他传递的参数会发生什么。当我交换func1和func2并在它期望4时用2个参数调用它时会发生什么。它是一个定义良好的行为,因为它适用于msvc,gcc,clang或者它是某种侥幸,我应该避免它。任何有更多专业知识的人都能详细说明这个话题吗?

2 个答案:

答案 0 :(得分:2)

  

这似乎可以正确地调用func1 [...]

您无法将std::function<Sig1>投射到std::function<Sig2>。它们是不相关的类型,尽管是相同功能模板的特化。人们不能简单地引用另一个。这是未定义的行为。未定义行为的一个潜在后果是代码似乎确实有效。然后你改变编译器。或编译器版本。或者只是一些随机无关的代码导致优化器执行不同的操作。还是......

如果您想要一个带有新签名的新函数,则必须创建一个新的函数对象。一种方法,如果你想简单地删除最后两个参数,那就是:

std::function<void(int, float, int, double)> func2 = [func1](int a, float b, int, double){
    func1(a, b);
};

另一种方法是利用bind简单地删除未使用的参数的事实:

std::function<void(int, float, int, double)> func2 = std::bind(func1, _1, _2);

这两个都很好。

答案 1 :(得分:1)

这个想法是能够通过字符串订阅事件,并且能够期望来自该事件的相等或更少的参数。以下是具有可疑功能的事件调度程序的代码。我认为这会起作用,因为std :: function的实现细节具有恒定的大小,其内部缓冲区的工作方式是固定的,如果它具有大小,则存储捕获,或者将指针存储到堆分配的存储捕获可以解释为什么重新解释转换有效,但我不确定调用函数时额外参数会发生什么。

#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>

template <typename F>
struct function_traits : public function_traits<decltype(&F::operator())>
{};

template <typename T, typename R, typename... Args>
struct function_traits<R(T::*)(Args...) const>
{
    typedef R(*pointer)(Args...);
    typedef R return_type;
    static constexpr std::size_t arg_count = sizeof...(Args);
    typedef std::tuple<Args...> args_tuple;
    typedef const std::function<R(Args...)> function;
};

struct function_wrapper
{
    virtual ~function_wrapper() {}
    virtual const void* get_ptr() const= 0;
};

template<typename F>
class function_wrapper_t : public function_wrapper
{
public:
    function_wrapper_t(F&& f) : _function(f) {}
    ~function_wrapper_t() {}
    const void* get_ptr() const { return &_function; }

private:
    typename function_traits<F>::function _function;
};

template <typename F>
std::unique_ptr<function_wrapper> create_wrapper(F f)
{
    return std::unique_ptr<function_wrapper_t<decltype(f)>>(new function_wrapper_t<decltype(f)>(std::forward<F>(f)));
}

class event_dispatcher
{
public:
    template<typename F>
    void connect(const std::string& name, F f)
    {
        static_assert(std::is_same<void, typename function_traits<F>::return_type>::value,
            "Signals cannot have a return type different from void");

        _list[name].emplace_back(create_wrapper(std::forward<F>(f)));
    }

    template<typename ... Args>
    void dispatch(const std::string& name, Args... args)
    {
        auto& funcs = _list[name];

        for (auto& func : funcs)
        {
            auto& f = *reinterpret_cast<const std::function<void(Args...)>*>(func->get_ptr());
            f(std::forward<Args>(args) ...); // is this undefined behavior?
        }
    }
private:
    std::unordered_map<std::string, std::vector<std::unique_ptr<function_wrapper>>> _list;
};

int main()
{
    event_dispatcher d;
    d.connect("test_event", [](int a, float b)
    {

    });
    d.connect("test_event", [](int a)
    {

    });

    d.dispatch("test_event", 1, 2.0f, 3, 4.0);
}