[英]C++17 vector of Generic (Polymorphic) lambdas
C ++ 14引入了通用的lambda(在lambda的簽名中使用auto關鍵字時)。
有沒有辦法用C ++ 17將它們存儲在向量中?
我知道這個現存的問題,但是它不適合我的需求: 我可以使用模板函數指針的std :: vector嗎?
這是示例代碼,說明了我想做什么。 (請在回答前查看底部的注釋)
#include <functional>
#include <vector>
struct A {
void doSomething() {
printf("A::doSomething()\n");
}
void doSomethingElse() {
printf("A::doSomethingElse()\n");
}
};
struct B {
void doSomething() {
printf("B::doSomething()\n");
}
void doSomethingElse() {
printf("B::doSomethingElse()\n");
}
};
struct TestRunner {
static void run(auto &actions) {
A a;
for (auto &action : actions) action(a);
B b;
for (auto &action : actions) action(b); // I would like to do it
// C c; ...
}
};
void testCase1() {
std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
actions.emplace_back([](auto &x) {
x.doSomething();
});
actions.emplace_back([](auto &x) {
x.doSomethingElse();
});
// actions.emplace_back(...) ...
TestRunner::run(actions);
}
void testCase2() {
std::vector<std::function<void(A&)>> actions; // Here should be something generic instead of A
actions.emplace_back([](auto &x) {
x.doSomething();
x.doSomethingElse();
});
actions.emplace_back([](auto &x) {
x.doSomethingElse();
x.doSomething();
});
// actions.emplace_back(...) ...
TestRunner::run(actions);
}
// ... more test cases : possibly thousands of them
// => we cannot ennumerate them all (in order to use a variant type for the actions signatures for example)
int main() {
testCase1();
testCase2();
return 0;
}
注意事項:
A
, B
和TestRunner
的代碼,只能更改測試用例的代碼 它遵循一個可能的解決方案(我不建議這樣做,但是您明確地說過,您不想討論它是好是壞,等等)。
根據要求, A
, B
和TestRunner
尚未更改(撇開auto
並不是TestRunner
的有效函數參數,我對此進行了相應設置)。
如果您可以稍微更改TestRunner
,則可以改善整個情況。
話雖如此,下面是代碼:
#include <functional>
#include <vector>
#include <iostream>
#include <utility>
#include <memory>
#include <type_traits>
struct A {
void doSomething() {
std::cout << "A::doSomething()" << std::endl;
}
void doSomethingElse() {
std::cout << "A::doSomethingElse()" << std::endl;
}
};
struct B {
void doSomething() {
std::cout << "B::doSomething()" << std::endl;
}
void doSomethingElse() {
std::cout << "B::doSomethingElse()" << std::endl;
}
};
struct Base {
virtual void operator()(A &) = 0;
virtual void operator()(B &) = 0;
};
template<typename L>
struct Wrapper: Base, L {
Wrapper(L &&l): L{std::forward<L>(l)} {}
void operator()(A &a) { L::operator()(a); }
void operator()(B &b) { L::operator()(b); }
};
struct TestRunner {
static void run(std::vector<std::reference_wrapper<Base>> &actions) {
A a;
for (auto &action : actions) action(a);
B b;
for (auto &action : actions) action(b);
}
};
void testCase1() {
auto l1 = [](auto &x) { x.doSomething(); };
auto l2 = [](auto &x) { x.doSomethingElse(); };
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
auto w2 = Wrapper<decltype(l2)>{std::move(l2)};
std::vector<std::reference_wrapper<Base>> actions;
actions.push_back(std::ref(static_cast<Base &>(w1)));
actions.push_back(std::ref(static_cast<Base &>(w2)));
TestRunner::run(actions);
}
void testCase2() {
auto l1 = [](auto &x) {
x.doSomething();
x.doSomethingElse();
};
auto l2 = [](auto &x) {
x.doSomethingElse();
x.doSomething();
};
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
auto w2 = Wrapper<decltype(l2)>{std::move(l2)};
std::vector<std::reference_wrapper<Base>> actions;
actions.push_back(std::ref(static_cast<Base &>(w1)));
actions.push_back(std::ref(static_cast<Base &>(w2)));
TestRunner::run(actions);
}
int main() {
testCase1();
testCase2();
return 0;
}
我看不到在向量中存儲非均質lambda的方法,因為它們只是具有非均質類型。
無論如何,通過定義一個接口(請參見Base
)並使用從給定接口和lambda繼承的模板類(請參見Wrapper
),我們可以將請求轉發給給定的通用lambda,並且仍然具有同質接口。
換句話說,解決方案的關鍵部分是以下類別:
struct Base {
virtual void operator()(A &) = 0;
virtual void operator()(B &) = 0;
};
template<typename L>
struct Wrapper: Base, L {
Wrapper(L &&l): L{std::forward<L>(l)} {}
void operator()(A &a) { L::operator()(a); }
void operator()(B &b) { L::operator()(b); }
};
可以從lambda創建包裝器的步驟如下:
auto l1 = [](auto &) { /* ... */ };
auto w1 = Wrapper<decltype(l1)>{std::move(l1)};
不幸的是,由於不修改TestRunner
的要求,我不得不使用std::ref
和std::reference_wrapper
才能將引用放入向量中。
在wandbox上看到它。
基本上,您想要的是std::function
的擴展。
std::function<Sig>
是類型可以擦除的可調用對象,可以對該特定簽名進行建模。 我們希望所有這些功能都具有更多簽名,並且所有這些簽名都可重載。 這變得棘手的地方是我們需要線性的過載堆棧。 該答案假定新的C ++ 17規則允許在using聲明中擴展參數包,並將從頭開始逐步構建。 同樣,這個答案並不專注於在必要時避免所有副本/電影,我只是在搭建腳手架。 另外,還需要更多SFINAE。
首先,我們需要給定簽名的虛擬調用運算符:
template <class Sig>
struct virt_oper_base;
template <class R, class... Args>
struct virt_oper_base<R(Args...)>
{
virtual R call(Args...) = 0;
};
還有一些東西可以將它們組合在一起:
template <class... Sigs>
struct base_placeholder : virt_oper_base<Sigs>...
{
virtual ~base_placeholder() = default;
using virt_oper_base<Sigs>::call...; // <3
virtual base_placeholder* clone() = 0; // for the copy constructor
};
現在煩人的部分。 我們需要一個placeholder<F, Sigs...>
來覆蓋每個call()
。 也許有更好的方法可以做到這一點,但是我能想到的最好的方法是擁有兩個類型列表模板參數,並在完成它們時將每個簽名從一個簽名移到另一個簽名:
template <class... >
struct typelist;
template <class F, class Done, class Sigs>
struct placeholder_impl;
template <class F, class... Done, class R, class... Args, class... Sigs>
struct placeholder_impl<F, typelist<Done...>, typelist<R(Args...), Sigs...>>
: placeholder_impl<F, typelist<Done..., R(Args...)>, typelist<Sigs...>>
{
using placeholder_impl<F, typelist<Done..., R(Args...)>, typelist<Sigs...>>::placeholder_impl;
R call(Args... args) override {
return this->f(args...);
}
};
template <class F, class... Done>
struct placeholder_impl<F, typelist<Done...>, typelist<>>
: base_placeholder<Done...>
{
placeholder_impl(F f) : f(std::move(f)) { }
F f;
};
template <class F, class... Sigs>
struct placeholder :
placeholder_impl<F, typelist<>, typelist<Sigs...>>
{
using placeholder_impl<F, typelist<>, typelist<Sigs...>>::placeholder_impl;
base_placeholder<Sigs...>* clone() override {
return new placeholder<F, Sigs...>(*this);
}
};
如果我繪制層次結構,這可能更有意義。 假設我們有您的兩個簽名: void(A&)
和void(B&)
:
virt_oper_base<void(A&)> virt_oper_base<void(B&)>
virtual void(A&) = 0; virtual void(B&) = 0;
↑ ↑
↑ ↑
base_placeholder<void(A&), void(B&)>
virtual ~base_placeholder() = default;
virtual base_placeholder* clone() = 0;
↑
placeholder_impl<F, typelist<void(A&), void(B&)>, typelist<>>
F f;
↑
placeholder_impl<F, typelist<void(A&)>, typelist<void(B&)>>
void call(B&) override;
↑
placeholder_impl<F, typelist<>, typelist<void(A&), void(B&)>>
void call(A&) override;
↑
placeholder<F, void(A&), void(B&)>
base_placeholder<void(A&), void(B&)>* clone();
我們需要一種檢查給定功能是否滿足簽名的方法:
template <class F, class Sig>
struct is_sig_callable;
template <class F, class R, class... Args>
struct is_sig_callable<F, R(Args...)>
: std::is_convertible<std::result_of_t<F(Args...)>, R>
{ };
現在,我們僅使用所有這些。 我們有頂級function
類,該類將具有base_placeholder
成員,該成員將管理其生存期。
template <class... Sigs>
class function
{
base_placeholder<Sigs...>* holder_;
public:
template <class F,
std::enable_if_t<(is_sig_callable<F&, Sigs>::value && ...), int> = 0>
function(F&& f)
: holder_(new placeholder<std::decay_t<F>, Sigs...>(std::forward<F>(f)))
{ }
~function()
{
delete holder_;
}
function(function const& rhs)
: holder_(rhs.holder_->clone())
{ }
function(function&& rhs) noexcept
: holder_(rhs.holder_)
{
rhs.holder_ = nullptr;
}
function& operator=(function rhs) noexcept
{
std::swap(holder_, rhs.holder_);
return *this;
}
template <class... Us>
auto operator()(Us&&... us)
-> decltype(holder_->call(std::forward<Us>(us)...))
{
return holder_->call(std::forward<Us>(us)...);
}
};
現在,我們有了帶有值語義的多簽名,類型擦除的函數對象。 您想要的只是:
std::vector<function<void(A&), void(B&)>> actions;
不可能以任何方式,形狀或形式存儲功能模板。 它們不是數據。 (函數也不是數據,但函數指針是)。 請注意,這里有std :: function,但是沒有std :: function_template。 有虛擬功能,但沒有虛擬功能模板。 有功能指針,但沒有功能模板指針。 這些都是一個簡單事實的體現:運行時沒有模板。
通用lambda只是具有operator()
成員函數模板的對象。 以上所有內容也適用於成員模板。
您可以獲得一組有限的,由編譯時確定的模板特化特性,使其表現得像一個對象,但這與僅具有有限的(可能是重載的)虛函數,函數指針或其他對象的對象沒有什么不同。 在您的情況下,這等效於
std::vector <
std::tuple <
std::function<void(A&)>,
std::function<void(B&)>
>
>
應該可以使用自定義轉換函數將通用lambda轉換為此類,甚至可以將ot包裝在具有operator()成員模板的對象中,因此從外部看,它看起來確實可以滿足您的要求- -但僅適用於類型A和B,而沒有其他功能。 要添加其他類型,您將必須向元組添加另一個元素。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.