Есть ли способ поддержать «константную ссылку» в качестве параметра сигнатуры функции в моей конструкции маршрутизатора общей функции? - PullRequest
1 голос
/ 08 апреля 2019

Я пытаюсь создать маршрутизатор функций, который вызывает правильную функцию из карты std::map<uint64_t, std::function<void(T)>>.Проблема в том, что он может найти только определенные виды функций с определенными сигнатурами функций.Я хочу, чтобы он поддерживал все виды функций.

Сама библиотека:

#ifndef ENGINE_H
#define ENGINE_H

#include <iostream>
#include <map>

class Engine
{
public:

  typedef std::uint64_t hash_t;

  /* Register function to signal router. */
  template<class T>
  void attach(hash_t hash, void(*f)(T)) {
    /* Cast function ptr to std::function. */
    auto func = static_cast<std::function<void (T)>>(f);
    signal_router<T>[hash] = func;
  }

  /* Call registerd function from signal router. */
  template<class T>
  void emit(hash_t hash, T&& param) {
    try {
      signal_router<T>[hash](param);
    } catch (std::bad_function_call&) {
      int status = -4;
      std::cerr << "Signal router: no function implemented for parameter \""
                << abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status) << "\" " << '\n';
    }
  }
private:
  template<typename T>
  static std::map<hash_t, std::function<void (T)>> signal_router;
};

/* We must declare static instance outside of its class, altough it's private. */
template<typename T>
typename::std::map<uint64_t, std::function<void (T)>> Engine::signal_router;

#endif /* ENGINE_H */

Использование:

#include <iostream>
#include <string>
#include <functional>
#include "engine.hpp"

void f1(int i) {
  std::cout << "Hello " << i << '\n';
}

void f2(long i) {
  std::cout << "Hello " << i << '\n';
}

void f3(std::string& i) {
  std::cout << "Hello " << i << '\n';
}

int main()
{
  Engine eng;

  eng.attach(0, f1);
  eng.emit(0, 1);

  eng.attach(1, f2);
  eng.emit(1, 10l);

  eng.attach(2, f3);
  std::string s = " world";
  eng.emit(2, s);

  return 0;
}

Выходы:

Hello 1
Hello 10
Hello world

Что правильно.

Но если я поменяю void f3(std::string& i) подпись на void f3(const std::string& i), это не получится.Как я понимаю, шаблонная функция создается с параметром const, но в какой-то момент она удаляется и не находит правильную функцию из карты функций.

Если изменить параметр функции f3 на const std::string&он выводит:

Signal router: no function implemented for parameter "std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >"

Таким образом, const удаляется.

Как я могу поддерживать все виды параметров (const ref, ref, values ​​и т.д ...) через мой дизайн шаблона

Ответы [ 2 ]

1 голос
/ 08 апреля 2019

Когда мы изначально присоединяем функцию, если параметр функции равен const, тогда безопасно также связать изменяемую версию:

template<class T>
void attach(hash_t hash, void(*f)(T)) {
  /* Bind to signal rounter */
  signal_router<T>[hash] = std::function<void(T)>(f); 

  /* Bind mutable version to signal router */
  using PlainT = std::remove_reference_t<T>; 
  if(std::is_reference<T>::value && std::is_const<PlainT>::value) {
      // Bind mutable version
      using MutableT = std::remove_const_t<PlainT>&;
      signal_router<MutableT>[hash] = std::function<void(MutableT)>(f); 
  }
}

Затем мы можем написать f3 как постоянную функцию:

void f3(std::string const& i) {
  std::cout << "Hello " << i << '\n';
}

А теперь main работает независимо от того, является ли std::string постоянным или нет.

Мы можем переписать это, также используя сопоставление с образцом:

template<class T>
void attach(hash_t hash, void(*f)(T)) {
  // if it's pass by value, add multiple binds for references
  signal_router<T>[hash] = std::function<void(T)>(f); 
  signal_router<T&>[hash] = std::function<void(T&)>(f); 
  signal_router<T const&>[hash] = std::function<void(T const&)>(f); 
  signal_router<T&&>[hash] = std::function<void(T&&)>(f); 
}
template<class T>
void attach(hash_t hash, void(*f)(T&)) {
  signal_router<T&>[hash] = std::function<void(T&)>(f); 
}
template<class T>
void attach(hash_t hash, void(*f)(const T&)) {
  signal_router<T const&>[hash] = std::function<void(T const&)>(f); 
  signal_router<T&>[hash] = std::function<void(T&)>(f); 
}
template<class T>
void attach(hash_t hash, void(*f)(T&&)) {
  signal_router<T&&>[hash] = std::function<void(T&&)>(f); 
}
0 голосов
/ 08 апреля 2019

Я предполагаю, что вы не поддерживаете volatile.

Вот 5 типов, которые могут быть в сигнатуре ваших указателей функций:

int
int const&
int &
int const&&
int &&

В вашем дизайне, вы не можете передать чистый int in. Поэтому нам нужно беспокоиться об этом только как аргумент указателя функции.

int может быть вызван любым из вышеперечисленных.

int const& может быть вызван любым из вышеперечисленных.

int const&& может быть вызван int&&.

int&, а int&& не может быть вызван ничем другим.

Теперь, если наш тип является подвижным, но не копируемым, правила меняются.

T можно вызвать только по T&&.

T const& все еще можновызывается кем-либо.

Если наш тип является неподвижным, то T не может быть вызван через прокси-упаковщик без системы emplace.

В точке вызова нам нужноинвертировать это.Если вызывается с T& T const& и T&.Если T можно скопировать, также проверьте T.

Если вызывается с T const&, мы проверяем только T const& и T, если T можно скопировать.

Если вызывается с T&&, нам нужно проверить T&& и T const&& и T const& и T, если T можно переместить.

Если вызывается с T const&&, мы проверяем только T const&& и T, если T можно скопировать.

Так что это дает нам план атаки.

template<class T>
void populate(has_t hash, std::function<void(T)> f) {
  signal_router<T>[hash] = std::move(f);
}

template<class T>
void attach(hash_t hash, void(*f)(T&)) {
  populate<T&>(hash, f);
}
template<class T>
void attach(hash_t hash, void(*f)(const T&)) {
  populate<T const&>(hash, f); 
  populate<T&>(hash, f); 
  populate<T&&>(hash, f); 
  populate<T const&&>(hash, f);
}
template<class T>
void attach(hash_t hash, void(*f)(T&&)) {
  populate<T&&>(hash, f);
}
template<class T>
void attach(hash_t hash, void(*f)(T const&&)) {
  populate<T&&>(hash, f);
  populate<T const&&>(hash, f);
}
template<class T>
void attach(hash_t hash, void(*f)(T)) {
  if constexpr( std::is_copy_constructible<T>{} ) {
    populate<T const&>(hash, f);
    populate<T&>(hash, f);
    populate<T const&&>(hash, f);
  }
  if constexpr( std::is_move_constructible<T>{} ) {
    populate<T&&>(hash, f);
  }
}

, а emit:

template<class T>
void emit(hash_t hash, T&& param) {
  try {
    signal_router<T&&>[hash](param);
  }

Для поддержки volatile потребуется еще один проход.

При этом использовался некоторый C ++ 17;Есть способы обойти if constexpr.Я бы использовал диспетчеризацию тегов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...