2017-02-19 31 views
1

У меня возник вопрос о том, чтобы сбрасывать std :: функцию одного типа в другой с тем же числом аргументов или больше, а затем вызывать его, поскольку он работает на всех компиляторах, но я не уверен, что это определенное поведение.casting std :: function для другой функции std :: и вызова его?

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); 

Это, кажется, правильно вызывает func1 с ожидаемыми аргументами 1, 2.0f. Что происходит с другими переданными аргументами. Что происходит, когда я меняю func1 и func2 и вызываю его с двумя аргументами, когда он ожидает 4. Является ли это четко определенным поведением, поскольку он работает на msvc, gcc, clang или это какая-то случайность, и я должен избегать этого. Может ли кто-нибудь с большим опытом разбираться по этой теме?

+0

Что такое прецедент? Просто любопытно. – Jagannath

+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

Идея заключалась в том, чтобы подписаться на событие по строке и чтобы можно было ожидать равных или меньших аргументов от этого события. Вот код для диспетчера событий, который имел сомнительную функциональность. Я думал, что это сработает из-за деталей реализации 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); 
}