Boost.Fusion run-time switch

前端 未结 5 1530
感情败类
感情败类 2021-02-13 17:43

I am reading the type of an object from a file:

enum class type_index { ... };
type_index typeidx = read(file_handle, type_index{});

Depending

相关标签:
5条回答
  • 2021-02-13 18:00

    When you say I have the same generic code for different types; is it possible to wrap it all into a function with same prototype?

    If so, you can map each type_index with a std::function in order to make the compiler to generate code for each type and to have an easy way to call every function in replacement to a switch.

    Switch replacement:

    function_map.at(read())();
    

    Running example:

    #include <stdexcept>
    #include <map>
    #include <string>
    #include <functional>
    #include <iostream>
    
    template<typename Type>
    void doGenericStuff() {
        std::cout << typeid(Type).name() << std::endl;
        // ...
    }
    
    class A {};
    class B {};
    enum class type_index {typeA, typeB};
    const std::map<type_index, std::function<void()>> function_map {
        {type_index::typeA, doGenericStuff<A>},
        {type_index::typeB, doGenericStuff<B>},
    };
    
    type_index read(void) {
        int i;
        std::cin >> i;
        return type_index(i);
    }
    
    int main(void) {
        function_map.at(read())(); // you must handle a possible std::out_of_range exception
        return 0;
    }
    
    0 讨论(0)
  • 2021-02-13 18:07

    I think your existing solution isn't bad. At the point of // do generic stuff instead call into other functions overloaded on type

    boost::fusion::for_each(possible_types, [&](auto i) {
      if (i::typeidx != typeidx) { return; }
      doSpecificStuff(i);
    });
    
    void doSpecificStuff(const TypeA& a) { ... }
    void doSpecificStuff(const TypeB& b) { ... }
    ...
    

    AFAIK you can't quite get a switch, which is a little bit faster than the if...else structure here, but not substantially and is unlikely to be noticeable for a process you run while reading a file.

    Other options are all similar to this. Fusion or mpl random access containers or even std::tuple can be access with get<> but that requires a compile time index so you're building the cases up and still going through the indices with something like

    if (idx == 0) { doSpecificStuff(std::get<0>(possible_types)); }
    else if (idx == 1) ...
    ....
    

    Which could be done with recursive templates, like:

    template <size_t current>
    void dispatchImpl(size_t idx)
    {
        if (idx >= std::tuple_size<possible_types>::value) return;
        if (idx == current) 
        {
            doSpecificStuff(std::get<current>(possible_types));
            return;
        }
        dispatchImpl<current + 1>(idx);
    }
    void dispatch(size_t idx) { dispatchImpl<0>(idx); }
    

    The only alternative I'm aware of would be building an array of function pointers. See Optimal way to access std::tuple element in runtime by index. I don't think you really gain anything with that solution for your case and it's harder to follow.

    One advantage to your fusion::for_each solution is that it doesn't force your type indices to be continuous. As your application evolves you can add new types or remove old types easily and the code still works, which would be harder if you were trying to use the container index as your type index.

    0 讨论(0)
  • 2021-02-13 18:09

    Build an unordered_map from type_index to processing code.

    Read the type_index, lookup in map, execute. Error check for missing entries.

    Simple, extendible, versionable -- simply add a length header on entries (make sure it handles 64 bit lengths -- have max lower bit count length mean real length is next, which allows single bit lengths to start), and if you do not understand an entry you can skip it.

    0 讨论(0)
  • 2021-02-13 18:13

    I would say the best thing would just be to use an array of functions that do what you want to do:

    typedef std::tuple<type1, type2, ..., typeN> PossibleTypes;
    typedef std::function<void()> Callback;
    
    PossibleTypes possible_types;
    std::array<Callback, std::tuple_size<PossibleTypes >::value> callbacks = {
        [&]{ doSomethingWith(std::get<0>(possible_types)); },
        [&]{ doSomethingElseWith(std::get<1>(possible_types)); },
        ...
    };
    

    That array is easy to generate with the help of integer_sequence, if all your calls are really the same:

    template <typename... T, size_t... Is>
    std::array<Callback, sizeof...(T)> makeCallbacksImpl(std::tuple<T...>& t,
                                                         integer_sequence<Is...>)
    {
        return { [&]{ doSomethingWith(std::get<Is>(t)) }... };
    
        // or maybe if you want doSomethingWith<4>(std::get<4>(t)):
        // return { [&]{ doSomethingWith<Is>(std::get<Is>(t)) }... };
    
    }
    
    template <typename... T>
    std::array<Callback, sizeof...(T)> makeCallbacks(std::tuple<T...>& t) {
        return makeCallbacksImpl(t, make_integer_sequence<sizeof...(T)>{});
    }
    

    And once we have our array, regardless of which way we generate, we just need to call it:

    void genericStuffWithIdx(int idx) {
        if (idx >= 0 && idx < callbacks.size()) {
            callbacks[idx]();
        }
        else {
            // some error handler
        }
    }
    

    Or if throwing is good enough:

    void genericStuffWithIdx(int idx) {
        callbacks.at(idx)(); // could throw std::out_of_range
    }
    

    You can't really beat array lookup on performance, although you do have indirection through std::function<void()>. This will definitely beat the fusion for_each solution, since there even if idx == 0, you're actually running through each element anyway. You would really want to use any() in that case, so you can quit early. But still faster to just use an array.

    0 讨论(0)
  • 2021-02-13 18:19

    I like my usual inherited lambdas trick:

    I've written about this before

    • Lambda functions as base classes
    • what is the correct way to handle multiple input command differently in c++? (where it visits members of a boost::variant)

    I believe I've seen Sumant Tambe use it in his more recent cpptruths.com postings.


    Demonstration

    Here's a demo for now. Will add some explanation later.

    The most important trick applied is that I use boost::variant to hide the type code denum for us. But the principle applies even if you keep your own type discrimination logic (just requiring more coding)

    Live On Coliru

    #include <boost/serialization/variant.hpp>
    #include <boost/serialization/vector.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    
    #include <fstream>
    #include <iostream>
    
    using namespace boost; // brevity
    
    //////////////////
    // This is the utility part that I had created in earlier answers:
    namespace util {
        template<typename T, class...Fs> struct visitor_t;
    
        template<typename T, class F1, class...Fs>
        struct visitor_t<T, F1, Fs...> : F1, visitor_t<T, Fs...>::type {
            typedef visitor_t type;
            visitor_t(F1 head, Fs...tail) : F1(head), visitor_t<T, Fs...>::type(tail...) {}
    
            using F1::operator();
            using visitor_t<T, Fs...>::type::operator();
        };
    
        template<typename T, class F> struct visitor_t<T, F> : F, boost::static_visitor<T> {
            typedef visitor_t type;
            visitor_t(F f) : F(f) {}
            using F::operator();
        };
    
        template<typename T=void, class...Fs>
        typename visitor_t<T, Fs...>::type make_visitor(Fs...x) { return {x...}; }
    }
    
    using util::make_visitor;
    
    namespace my_types {
        //////////////////
        // fake types for demo only
        struct A1 {
            std::string data;
        };
    
        struct A2 {
            double data;
        };
    
        struct A3 {
            std::vector<int> data;
        };
    
        // some operations defined on A1,A2...
        template <typename A> static inline void serialize(A& ar, A1& a, unsigned) { ar & a.data; } // using boost serialization for brevity
        template <typename A> static inline void serialize(A& ar, A2& a, unsigned) { ar & a.data; } // using boost serialization for brevity
        template <typename A> static inline void serialize(A& ar, A3& a, unsigned) { ar & a.data; } // using boost serialization for brevity
    
        static inline void display(std::ostream& os, A3 const& a3) { os << "display A3: " << a3.data.size() << " elements\n"; }
        template <typename T> static inline void display(std::ostream& os, T const& an) { os << "display A1 or A2: " << an.data << "\n"; }
    
        //////////////////
        // our variant logic
        using AnyA = variant<A1,A2,A3>;
    
        //////////////////
        // test data setup
        AnyA generate() { // generate a random A1,A2...
            switch (rand()%3) {
                case 0: return A1{ "data is a string here" };
                case 1: return A2{ 42 };
                case 2: return A3{ { 1,2,3,4,5,6,7,8,9,10 } };
                default: throw std::invalid_argument("rand");
            }
        }
    
    }
    
    using my_types::AnyA;
    
    void write_archive(std::string const& fname) // write a test archive of 10 random AnyA
    {
        std::vector<AnyA> As;
        std::generate_n(back_inserter(As), 10, my_types::generate);
    
        std::ofstream ofs(fname, std::ios::binary);
        archive::text_oarchive oa(ofs);
    
        oa << As;
    }
    
    //////////////////
    // logic under test
    template <typename F>
    void process_archive(std::string const& fname, F process) // reads a archive of AnyA and calls the processing function on it
    {
        std::ifstream ifs(fname, std::ios::binary);
        archive::text_iarchive ia(ifs);
    
        std::vector<AnyA> As;
        ia >> As;
    
        for(auto& a : As)
            apply_visitor(process, a);
    }
    
    int main() {
        srand(time(0));
    
        write_archive("archive.txt");
    
        // the following is c++11/c++1y lambda shorthand for entirely compiletime
        // generated code for the specific type(s) received
        auto visitor = make_visitor(
            [](my_types::A2& a3) { 
                    std::cout << "Skipping A2 items, just because we can\n";
                    display(std::cout, a3);
                },
            [](auto& other) { 
                    std::cout << "Processing (other)\n";
                    display(std::cout, other);
                }
            );
    
        process_archive("archive.txt", visitor);
    }
    

    Prints

    Processing (other)
    display A3: 10 elements
    Skipping A2 items, just because we can
    display A1 or A2: 42
    Processing (other)
    display A1 or A2: data is a string here
    Processing (other)
    display A3: 10 elements
    Processing (other)
    display A1 or A2: data is a string here
    Processing (other)
    display A1 or A2: data is a string here
    Processing (other)
    display A3: 10 elements
    Processing (other)
    display A1 or A2: data is a string here
    Processing (other)
    display A3: 10 elements
    Processing (other)
    display A3: 10 elements
    
    0 讨论(0)
提交回复
热议问题