所以我们可以用一种通用的方式来做到这一点。
template<class...>struct types{};
template<std::size_t I>using index=std::integral_constant<std::size_t, I>;
template<class T, class types>
struct get_index_of_type;
template<class T, class...Ts>
struct get_index_of_type<T, types<T,Ts...>>:
index<0>
{};
template<class T, class U, class...Ts>
struct get_index_of_type<T, types<U,Ts...>>:
index<get_index_of_type<T, types<Ts...>>{}+1>
{};
template<class R, class Types>
struct dynamic_dispatch;
template<class R, class...Ts>
struct dynamic_dispatch<R, types<Ts...>>
{
using fptr = R(*)(void const* pf, void* t);
template<class F>
std::array<fptr, sizeof...(Ts)>
make_table() const {
return {{
+[](void const* pf, void* t)->R{
auto* pt = static_cast< std::remove_reference_t<Ts>* >(t);
auto* f = static_cast< std::remove_reference_t<F> const* >(pf);
return (*f)(static_cast<Ts&&>(*pt));
}...
}};
}
void const* pf = nullptr;
std::array<fptr, sizeof...(Ts)> table;
dynamic_dispatch( dynamic_dispatch&& )=default;
dynamic_dispatch( dynamic_dispatch const& )=default;
dynamic_dispatch& operator=( dynamic_dispatch&& )=default;
dynamic_dispatch& operator=( dynamic_dispatch const& )=default;
template<class F,
std::enable_if_t< !std::is_same<std::decay_t<F>, dynamic_dispatch>{}, int> =0
>
dynamic_dispatch( F&& f ):
pf(std::addressof(f)),
table( make_table<std::decay_t<F>>() )
{}
template<class T>
R operator()( T&& t ) const {
return table[get_index_of_type<T,types<Ts...>>{}]( pf, std::addressof(t) );
}
};
dynamic_dispatch<R, types<a,b,c>> 接受任何可以用a、b 或c 调用的任何可调用对象(准确的类型,包括所需的 l/r 值和 const,因此请让您的列表冗长。没有隐式转换完成;这可以通过更多工作来解决)。
现在,在base 中添加一个名为apply 的方法:
virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) {
return f(this);
}
在派生中覆盖它:
virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) override {
return f(this);
}
具有相同的身体。
现在主要:
auto super_foo = [](auto* x) {
return foo(*x);
};
int main()
{
base* ptr1 = new(base);
base* ptr2 = new(derived);
ptr1->apply(super_foo);
ptr2->apply(super_foo);
}
live example.
为了进一步阅读,我将擦除的动态调度键入到函数对象的类型列表中。我为此调度创建了一个视图。
super_foo 是代表foo 的整个重载集的单个对象,允许将其作为一个参数传递。
这也可以通过访问者模式更传统地完成:
struct visitor {
void invoke( base* ) const = 0;
void invoke( derived* ) const = 0;
};
然后你实现一个foo_visitor:
struct foo_visitor:visitor {
void invoke( base* a ) const {return foo(*a);}
void invoke( derived* a ) const {return foo(*a);}
};
我们写了一个apply,它接受一个visitor&并在上面做.invoke(this)。
请注意,这种技术,特别是如果您巧妙地在 Ts 之间进行选择,而不是要求完全匹配,则允许通过递归实现多个调度多态性(或者,您可以使用多个类型包将 dynamic_dispatch 更改为 dynamic_dispatch< R, types... > )。