在我的通用功能路由器设计中,是否有一种方法可以将“ const reference”作为功能签名参数支持?

时间:2019-04-08 18:58:01

标签: c++ c++11 templates

我正在尝试制作一个功能路由器,以从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,value等)?

2 个答案:

答案 0 :(得分:1)

当我们最初附加函数时,如果函数参数为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编写为const函数:

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

答案 1 :(得分:0)

我将假设您不支持volatile。

以下是函数指针签名中的5种类型:

render()

在您的设计中,您不能传递纯int int const& int & int const&& int && 。因此,我们只需要担心它作为函数指针参数。

int可以被上述任何一种调用。

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调用,我们只会检查TT 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); } } 支持需要另外一遍。

这使用了一些C ++ 17; template<class T> void emit(hash_t hash, T&& param) { try { signal_router<T&&>[hash](param); } 周围有很多方法。我会使用标签分配。