有没有办法在我的通用功能路由器设计中支持"常量引用"作为功能签名参数?

Is there a way to support 'const reference' as a function signature parameter in my generic function router design?

本文关键字:功能 引用 常量 参数 支持 我的 有没有 路由器      更新时间:2023-10-16

我正在尝试制作一个从映射中调用正确函数的功能路由器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,那么绑定一个可变版本也是安全的:

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

而现在,无论std::string是否恒定,main都有效。

我们也可以使用模式匹配来重写它:

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

我假设你不支持易失性。

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

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

在你的设计中,你不能传递一个纯粹的int。 因此,我们只需要将其视为函数指针参数即可。

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

int const&可以通过上述任何一种调用。

int const&&可以由int&&调用。

int&int&&不能被其他任何东西调用。

现在,如果我们的类型是可移动但不可复制的,则规则会更改。

T只能由T&&调用。

T const&仍然可以被任何人调用。

如果我们的类型是不可移动的,那么在没有emplace系统的情况下,就无法通过代理包装器调用T

在调用时,我们需要反转这一点。 如果用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);
}
}

并且发出是:

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

volatile支持将需要另一次传递。

这使用了大约 C++17; 有办法绕过if constexpr。 我会使用标签调度。