模板类型擦除
Template type erasure
我想知道是否有一种实用的方法可以使用 C++17 标准编写如下代码:
#include <string>
#include <functional>
#include <unordered_map>
template <class Arg>
struct Foo
{
using arg_type = Arg;
using fun_type = std::function< void(Arg&) >;
fun_type fun;
void call( Arg& arg ) { fun(arg); }
};
struct Bar
{
using map_type = std::unordered_map<std::string,Foo>; // that's incorrect
map_type map;
auto& operator[] ( std::string name ) { return map[name]; }
};
在上面的代码中,class Foo
的模板参数对应于一些一元函数的输入类型,returns 没有。具有不同模板类型的 Foo
的不同实例对应于采用不同类型参数的函数。 class Bar
只是为了给这些函数起一个名字,但是很明显,当前映射的声明是不正确的,因为它需要知道 Foo
.[=15 的模板类型=]
或者是吗?
不幸的是,通过编译时检查来执行此操作是不可行的。但是,您可以通过运行时检查来提供该功能。
一个地图的值类型只能是一种类型,Foo<T>
是每个 T
的不同类型。然而,我们可以通过给每个 Foo<T>
一个公共基础 class,有一个指向它的指针映射,并使用虚函数将 call()
分派给适当的子 [=] 来解决这个问题28=].
为此,参数的类型也必须始终相同。正如@MSalters 所提到的,std::any
可以提供帮助。
最后,我们可以使用 pimpl 模式包装所有这些,这样看起来只有一个整洁的 Foo
类型:
#include <cassert>
#include <string>
#include <functional>
#include <any>
#include <unordered_map>
#include <memory>
struct Foo {
public:
template<typename T, typename FunT>
void set(FunT fun) {
pimpl_ = std::make_unique<FooImpl<T, FunT>>(std::move(fun));
}
// Using operator()() instead of call() makes this a functor, which
// is a little more flexible.
void operator()(const std::any& arg) {
assert(pimpl_);
pimpl_->call(arg);
}
private:
struct IFooImpl {
virtual ~IFooImpl() = default;
virtual void call( const std::any& arg ) const = 0;
};
template <class Arg, typename FunT>
struct FooImpl : IFooImpl
{
FooImpl(FunT fun) : fun_(std::move(fun)) {}
void call( const std::any& arg ) const override {
fun_(std::any_cast<Arg>(arg));
}
private:
FunT fun_;
};
std::unique_ptr<IFooImpl> pimpl_;
};
// Usage sample
#include <iostream>
void bar(int v) {
std::cout << "bar called with: " << v << "\n";
}
int main() {
std::unordered_map<std::string, Foo> table;
table["aaa"].set<int>(bar);
// Even works with templates/generic lambdas!
table["bbb"].set<float>([](auto x) {
std::cout << "bbb called with " << x << "\n";
});
table["aaa"](14);
table["bbb"](12.0f);
}
我想知道是否有一种实用的方法可以使用 C++17 标准编写如下代码:
#include <string>
#include <functional>
#include <unordered_map>
template <class Arg>
struct Foo
{
using arg_type = Arg;
using fun_type = std::function< void(Arg&) >;
fun_type fun;
void call( Arg& arg ) { fun(arg); }
};
struct Bar
{
using map_type = std::unordered_map<std::string,Foo>; // that's incorrect
map_type map;
auto& operator[] ( std::string name ) { return map[name]; }
};
在上面的代码中,class Foo
的模板参数对应于一些一元函数的输入类型,returns 没有。具有不同模板类型的 Foo
的不同实例对应于采用不同类型参数的函数。 class Bar
只是为了给这些函数起一个名字,但是很明显,当前映射的声明是不正确的,因为它需要知道 Foo
.[=15 的模板类型=]
或者是吗?
不幸的是,通过编译时检查来执行此操作是不可行的。但是,您可以通过运行时检查来提供该功能。
一个地图的值类型只能是一种类型,Foo<T>
是每个 T
的不同类型。然而,我们可以通过给每个 Foo<T>
一个公共基础 class,有一个指向它的指针映射,并使用虚函数将 call()
分派给适当的子 [=] 来解决这个问题28=].
为此,参数的类型也必须始终相同。正如@MSalters 所提到的,std::any
可以提供帮助。
最后,我们可以使用 pimpl 模式包装所有这些,这样看起来只有一个整洁的 Foo
类型:
#include <cassert>
#include <string>
#include <functional>
#include <any>
#include <unordered_map>
#include <memory>
struct Foo {
public:
template<typename T, typename FunT>
void set(FunT fun) {
pimpl_ = std::make_unique<FooImpl<T, FunT>>(std::move(fun));
}
// Using operator()() instead of call() makes this a functor, which
// is a little more flexible.
void operator()(const std::any& arg) {
assert(pimpl_);
pimpl_->call(arg);
}
private:
struct IFooImpl {
virtual ~IFooImpl() = default;
virtual void call( const std::any& arg ) const = 0;
};
template <class Arg, typename FunT>
struct FooImpl : IFooImpl
{
FooImpl(FunT fun) : fun_(std::move(fun)) {}
void call( const std::any& arg ) const override {
fun_(std::any_cast<Arg>(arg));
}
private:
FunT fun_;
};
std::unique_ptr<IFooImpl> pimpl_;
};
// Usage sample
#include <iostream>
void bar(int v) {
std::cout << "bar called with: " << v << "\n";
}
int main() {
std::unordered_map<std::string, Foo> table;
table["aaa"].set<int>(bar);
// Even works with templates/generic lambdas!
table["bbb"].set<float>([](auto x) {
std::cout << "bbb called with " << x << "\n";
});
table["aaa"](14);
table["bbb"](12.0f);
}