訪問者訪問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