访问者访问std::function
的方式是更改元素的接受部分。作为代价,你会失去双重调度,但是你会将迭代的样板抽象一些。
代替元件上的一个accept
方法的,具有每accept
样探视之一。
如果您想在标准访问者中以多种方式访问事物,则需要编写更多访问者类型,并添加新的重载接受它们。
在std::function
基于一个,你只要写一个新的accept
型功能用不同的名称;该名称以方法的名称出现,而不是以访客类型的名称(因为访客类型是匿名的)。
在使用SFINAE std::function
智能的C++ 14中,您可以使用一个超载的accept
,但是您必须将“访问标签”传递给访问者以确定它期望的访问类型。这可能不值得麻烦。
第二个问题是std::function
不支持多重参数类型的重载。访问者的用途之一是我们根据元素的动态类型进行不同的调度 - 完全双派遣。
作为一个具体的案例研究,设想3种访问:保存,加载和显示。保存和显示的主要区别在于显示器清除不可见的事物(遮挡或设置为不可见)。
在传统的元素/访问者下,您将有一个接受函数带有3个过载,每个接受函数的取值为Saver*
或Loader*
或Displayer*
。 Saver
Loader
和Displayer
中的每一个都有一堆visit(element*)
和visit(derived_element_type*)
方法。
在std::function
访问下,您的元素改为save(std::function<void(element*)>
和load(
和display(
方法。没有双重调度完成,因为std::function
只公开一个接口。
现在,我们可以编写一个std::function
-esque多重调度的超载机制,如果我们需要的话。不过这是先进的C++。
template<class Is, size_t I>
struct add;
template<class Is, size_t I>
using add_t=typename add<Is,I>::type;
template<size_t...Is, size_t I>
struct add<std::index_sequence<Is...>, I>{
using type=std::index_sequence<(I+Is)...>;
};
template<template<class...>class Z, class Is, class...Ts>
struct partial_apply;
template<template<class...>class Z, class Is, class...Ts>
using partial_apply_t=typename partial_apply<Z,Is,Ts...>::type;
template<template<class...>class Z, size_t...Is, class...Ts>
struct partial_apply<Z,std::index_sequence<Is...>, Ts...> {
using tup = std::tuple<Ts...>;
template<size_t I> using e = std::tuple_element_t<I, tup>;
using type=Z< e<Is>... >;
};
template<template<class...>class Z, class...Ts>
struct split {
using left = partial_apply_t<Z, std::make_index_sequence<sizeof...(Ts)/2>, Ts...>;
using right = partial_apply_t<Z, add_t<
std::make_index_sequence<(1+sizeof...(Ts))/2>,
sizeof...(Ts)/2
>, Ts...>;
};
template<template<class...>class Z, class...Ts>
using right=typename split<Z,Ts...>::right;
template<template<class...>class Z, class...Ts>
using left=typename split<Z,Ts...>::left;
template<class...Sigs>
struct functions_impl;
template<class...Sigs>
using functions = typename functions_impl<Sigs...>::type;
template<class...Sigs>
struct functions_impl:
left<functions, Sigs...>,
right<functions, Sigs...>
{
using type=functions_impl;
using A = left<functions, Sigs...>;
using B = right<functions, Sigs...>;
using A::operator();
using B::operator();
template<class F>
functions_impl(F&& f):
A(f),
B(std::forward<F>(f))
{}
};
template<class Sig>
struct functions_impl<Sig> {
using type=std::function<Sig>;
};
,让你支持多个签名(但只有一个功能)的std::function
。要使用它,你可以试试:
functions< void(int), void(double) > f = [](auto&& x){std::cout << x << '\n'; };
,当与int
调用,打印一个int,并且当与double
调用,打印双。如上所述,这是高级的C++:我只是简单地将它包含在内以表明该语言足够强大,可以处理该问题。
Live example。
使用该技术,您可以使用std::function
类型接口进行双重调度。简单的访问者必须传递一个可调用的函数,可以处理您发送的每个超载,而且您的元素必须详细说明访问者在其签名中支持的所有类型。
你会注意到,如果你实现这个,你会在访问视域中得到一些非常神奇的多态。你将被动态地调用你正在访问的东西的静态类型,你只需要编写一个方法体。在合同中添加新的需求(在accept方法的接口声明处),而不是2 + K,如经典访问(在accept方法中,在访问类型的接口中以及在各种重载中的访问类(这可以通过我会承认的CRTP来消除))。
以上functions<Sigs...>
存储该函数的N个副本。一个更优化的可以存储一次tur函数和N个调用视图。这是一个更难触摸,但只是一个触摸。
template<class...Sigs>
struct efficient_storage_functions:
functions<Sigs...>
{
std::unique_ptr<void, void(*)(void*)> storage;
template<class F> // insert SFINAE here
efficient_storage_functions(F&& f):
storage{
new std::decay_T<F>(std::forward<F>(f)),
[](void* ptr){
delete static_cast<std::decay_t<F>*>(ptr);
}
},
functions<Sigs...>(
std::reference_wrapper<std::decay_t<F>>(
get<std::decay_t<F>>()
)
)
{}
template<class F>
F& get() {
return *static_cast<F*>(storage.get());
}
template<class F>
F const& get() const {
return *static_cast<F const*>(storage.get());
}
};
其未来的需求与小目标优化来改善(不存储在堆栈上的类型)和SFINAE支持,因此它并不试图从东西是不兼容的构建。
它存储一个unique_ptr
来电可调用的副本,它所继承的无数std::function
从所有存储std::reverence_wrapper
其内容。
还缺少的是复制构造。
那么它当然可以工作,但它可能并不清楚什么每个访问者的行为将是。我能有您的实现下参观不同的两个同类型的游客。 – AndyG
我在考虑转发参数。我看了一场关于斯科特迈耶斯关于转发的谈话,并且非常害怕。这可能会危险吗? – Incomputable
没有,转发你的论点保留L值或它们的R值的性质。如果你从一个你不应该拥有的物体移开,那只会有危险。 – AndyG