Search code examples
c++macrosswitch-statementpreprocessor

How to iterate in-macro integer value in a variable-parameter-length C++ macro (... and VA_ARGS)?


I am trying to write a C++ macro which would substitue a frequently-needed verbose code like

switch (id) {
case 0:
    s << myFloat;
    break;
case 1:
    s << myInt;
    break;
default: break;
}

with something like DESERIALIZE_MEMBERS(myFloat, myInt). s and id will not change names for the use case, so they don't need to be macro parameters.

It should support variable argument length, so DESERIALIZE_MEMBERS(myString, myArrayOfInts, myLong) for another case should also work, adding a third case statement to the switch expression.

However, it's not clear to me how to iterate the N value in case N: inside the macro for each argument.

Is that possible at all in standard C++ macros?


Solution

  • In , here is a solution.

    First a compile-time constant type and value:

    template<auto X>
    using constant_t = std::integral_constant<decltype(X), X>;
    template<auto X>
    constexpr constant_t<X> constant_v;
    

    Next, a variant over such constants:

    template<auto...Is>
    using enum_t = std::variant<constant_t<Is>...>;
    

    this lets you generate a compile-time enumeration value at runtime and use it.

    Now some code that lets you convert a runtime integer into a compile-time enumeration value:

    template<std::size_t...Is>
    constexpr enum_t<Is...> get_enum_v( std::index_sequence<Is...>, std::size_t i ) {
      using generator = enum_t<Is...>(*)();
      constexpr generator generators[] = {
        +[]()->enum_t<Is...> {
          return constant_v<Is>;
        }...
      };
      return generators[i]();
    }
    template<std::size_t N>
    auto get_enum_v( std::size_t i ) {
      return get_enum_v( std::make_index_sequence<N>{}, i );
    }
    

    so if you do get_enum_v<10>(2), it returns an enum_t<...> with 10 alternatives containing the alternative with index 2, which is a constant_v<std::size_t, 2>.

    Now we just get a tuple and an index, and we call a function on the tuple element described by the index:

    template<class F, class Tuple>
    auto apply_to_nth_element( std::size_t i, F f, Tuple tuple ) {
      constexpr std::size_t N = std::tuple_size<Tuple>{};
      auto Index = get_enum_v<N>( i );
      return std::visit( [&](auto I){
        return f( std::get<I>(tuple) );
      }, Index );
    }
    

    you can now do this:

    apply_to_nth_element(id, [&](auto& elem) {
      s << elem;
    }, std::tie(myFloat, myInt));
    

    instead of

    DESERIALIZE_MEMBERS(myFloat, myInt)
    

    Live example; Code can be rewritten in versions older than but gets extremely ugly very fast.