2017-02-16 34 views
9

C++ 14引入通用lambda(在lambda的簽名中使用auto關鍵字時)。通用(多形)lambda的C++ 17矢量

有沒有辦法將它們存儲在一個向量與C + + 17?

我知道這個存在的問題,但它不適合我的需求:Can I have a std::vector of template function pointers?

這是說明我希望做一個什麼樣的代碼。 (請參閱底部的筆記纔回答)

#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; 
} 

注:

  • ABTestRunner的代碼不能改變,測試用例只有代碼
  • 我不我不想討論像這樣編碼測試是好還是不好,這是無關緊要的(這裏使用的測試術語只是爲了說明我不能枚舉所有的lambda表達式(爲了使用它們的變體類型.. ))

回答

6

它遵循一個可能的解決方案(我不會推薦,但你明確表示你不想討論它是好還是錯等)。
根據要求,ABTestRunner沒有改變(拋開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); } 
}; 

在哪裏的包裝可以從拉姆達被創建,因爲它遵循:

auto l1 = [](auto &) { /* ... */ }; 
auto w1 = Wrapper<decltype(l1)>{std::move(l1)}; 

不幸的是,對於的要求是爲了不修改TestRunner,我必須使用std::refstd::reference_wrapper才能將引用放入向量中。

請參閱wandbox

+0

auto是C++ 14中TestRunner的有效函數參數(https://ideone.com/4931Ht) – infiniteLoop

+1

多形態解決方案非常聰明+1。 –

+1

@infiniteLoop使用'auto'在[wandbox]上查看它(http://melpon.org/wandbox/permlink/qrrFmVRri4bxM0cy)。請注意,這是一個GCC擴展,而不是C++ 14中用於免費函數的有效參數。 – skypjack

0

基本上你想要的是std::function的擴展。

std::function<Sig>是一種可以模擬特定簽名的類型擦除可調用函數。我們需要所有這些功能,但需要更多的簽名,並且所有這些簽名都可以重載。這變得棘手的地方是我們需要線性堆棧的重載。這個答案假定新的C++ 17規則允許在使用聲明中擴展參數包,並且將從頭開始分段構建。此外,這個答案沒有集中在必要時避免所有的副本/電影,我只是建造腳手架。此外,還需要更多的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; 
0

不可能以任何方式,形狀或形式存儲功能模板。他們不是數據。 (函數也不是數據,但函數指針是)。注意有std :: function,但沒有std :: function_template。有虛擬功能,但沒有虛擬功能模板。有函數指針,但沒有函數模板指針。這些都是一個簡單事實的表現:運行時沒有模板。

一般的lambda只是一個帶有operator()成員函數模板的對象。以上所有內容也適用於成員模板。

你可以得到一組有限的,編譯時間確定的模板特化,像一個對象一樣工作,但這與僅僅有一堆(可能是過載的)虛擬函數或函數指針或其他類型的對象沒有什麼不同。在您的情況,這是有一個

std::vector < 
    std::tuple < 
     std::function<void(A&)>, 
     std::function<void(B&)> 
    > 
> 

它應該可以通用拉姆達轉換爲這樣一對用自定義轉換功能,甚至在有操作者的物體包裹OT()成員的等效模板,所以從外面看,它看起來就像你想要的一樣 - 但它只能用於類型A和B,而沒有別的。要添加另一個類型,您將不得不向元組添加另一個元素。