2017-02-16 5 views
9

C++ 14は、ラムダのシグネチャでautoキーワードを使用している場合、一般的なラムダを導入します。C++ 17ジェネリック(多型)ラムダのベクトル

C++でベクターに保存する方法はありますか?17

私はこの既存の問題について知っているが、それは私のニーズに合わせていません。ここでCan I have a std::vector of template function pointers?

は私がやりたいものを例示するサンプルコードです。

    ABTestRunnerのコードを変更することはできません
  • 、テストケースのコードのみ
  • Iドン:

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

    NOTES(応答する前に下部の注意事項をご参照ください)このようなテストをコード化するのが良いのか間違っているのかを議論したいのですが、これは話題にはなりません(テスト用語はここではすべてのlambdaを列挙できないことを示すためにのみ使用しています))

答えて

6

それは可能な解決策に従っています(私はお勧めしませんが、あなたはそれが良いか間違っているかなどについて議論したくないと明言しました)。
A,BおよびTestRunnerは変更されていません(は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; 
} 

彼らは単に不均質なタイプを持っているため、私は、ベクター中の非均質ラムダを格納するための方法を見ることはできません。
インターフェイスを定義し(Baseを参照)、指定されたインターフェイスとラムダを継承するテンプレートクラス(Wrapperを参照)を使用して、指定された汎用ラムダに要求を転送することができます。他の点で
は、ソリューションの重要な部分は、以下のクラスです:

それは次のようにラッパーはラムダから作成することができます
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(https://ideone.com/4931Ht)のTestRunnerの有効な関数パラメータです。 – infiniteLoop

+1

その多態性の解は非常に賢い+1です。 –

+1

@infiniteLoop [auto]で[wandbox](http://melpon.org/wandbox/permlink/qrrFmVRri4bxM0cy)で実行してみてください。これはGCCの拡張であり、C++ 14の空き関数の有効なパラメータではないことに注意してください。 – skypjack

0

基本的には、拡張子はstd::functionです。

std::function<Sig>は、その特定の署名をモデル化することができる型消去された呼び出し可能型です。私たちは、その機能の全てを望んでいますが、より多くのシグネチャを持っており、それらのシグネチャのすべてがオーバーロード可能になっています。これが面倒になるところでは、過負荷の線形スタックが必要です。この回答は、新しいC++ルールでは、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()そこにこれを行うには良い方法かもしれませんが、私はあると考えることができる最高の方法は、2つのtypelistにテンプレートパラメータを持っていると我々は彼らと仕上げとしてだけで一方から他方への各署名を移動するには:

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

そして今、私たちはまさにそれのすべてを使用するのは、私たちはあなたの2人の署名を持っているとしましょう。私たちはトップレベルの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はありません。仮想関数はありますが、仮想関数テンプレートはありません。関数ポインタはありますが、関数テンプレートポインタはありません。これらはすべて、単純な事実の現れです:実行時にテンプレートはありません。

汎用ラムダは、メンバー関数テンプレートoperator()を持つオブジェクトです。上記のすべてがメンバーテンプレートにも当てはまります。

オブジェクトのように振る舞うように、コンパイル時に決定される有限のテンプレート特殊化セットを得ることができます。ただし、(オーバーロードされている可能性のある)仮想関数や関数ポインタなどの有限集合を持つオブジェクトと変わりません。あなたの状況では、それは

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

を持つのと同等のITは、カスタム変換機能で、このようなペアに一般的なラムダを変換することが可能でなければならない、あるいはオペレータ()メンバーを持つオブジェクトにOTラップテンプレートのように、外部からはあなたが望むものとまったく同じように見えますが、タイプAとタイプBのみで動作します。別の型を追加するには、別の要素をタプルに追加する必要があります。