c++c++20boost-mp11

Using boost mp11 to switch on runtime value efficiently(breaking when processing function is done)


I have the following code where I implement dispatching on runtime value to interpret the data in certain way(in this toy example data can be either uint8_t or short).

Code seems to work, but I am wondering if I can somehow microoptimize the code so that when I have a hit(processing function matches) processing is stopped (currently even if first element of tuple is a "handler" entire tuple is iterated over at runtime).

#include <boost/mp11/tuple.hpp>
#include <iostream>

uint8_t data[4] = {0,1,100,2};

template<int runtimeId, typename T>
struct kindToType{
    static constexpr int id =  runtimeId;
    using type = T;
};

 const auto print  =[]<typename T> (const T* data){
    if constexpr(std::is_same_v<short, std::remove_cvref_t<T>>){
        const short* values = (const short*)data;
        std::cout << values[0] << "  " << values[1] << std::endl;
    } else if constexpr(std::is_same_v<uint8_t, std::remove_cvref_t<T>>){
        const uint8_t* values = (const uint8_t*)data;
    std::cout << (int)values[0] << "  " << (int)values[1]<< "  " << (int)values[2] << "  " << (int)values[3] << std::endl;;
    }
};

static constexpr std::tuple<kindToType<10, uint8_t>, kindToType<11, short>> mappings{};

void dispatch(int kind){
    boost::mp11::tuple_for_each(mappings, [kind]<typename Mapping>(const Mapping&) {
    if (Mapping::id == kind)
    {
        print((typename Mapping::type*)data);
    }
    });
}
int main()
{
    // no guarantee that kind is index like(e.g. for two values
    // it can have values 47 and 1701)
    dispatch(10);
    dispatch(11);
}

Notes:


Solution

  • The runtime performance of this heavily depends on the size of your tuple. You can make your own for_each_tuple implementation that does an early out when your function gets executed:

    template<typename FuncTuple, typename Selector>
    void tuple_for_each(FuncTuple const& funcTuple, Selector selector) 
    {
        std::apply([selector](auto const& ...funcs)
        {
            (void)(selector(funcs) || ...);
        }, funcTuple);
    }
    

    your dispatch would then look like this:

    void dispatch(int kind)
    {
        tuple_for_each(mappings, [kind]<typename Mapping>(const Mapping&) 
        {
            std::cout << "loop, ";
            if (Mapping::id == kind)
            {
                print((typename Mapping::type*)data);
                return true;
            }
            return false;
        });
    }
    

    If you get rid of the template in your lambda and use auto instead this code will compile with C++17. We use operator short circuiting to our advantage so the compiler will provide an early out for us. Here is the full code.

    Also, note that the cast (const short*)data is UB.