Search code examples
c++c++20boost-mp11

Apply stateful lambda to integer sequence values


I am playing around with trying to implement the numeric literal operator template.

#include <string_view>
#include <cstdint>
#include <cmath>
#include <iostream>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/mp11/algorithm.hpp>
using namespace boost::mp11;

template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){
    int weight =std::pow(10, sizeof... (Cs));
    // unused, would like to transform it using lambda that mutably captures
    // weight
    using ints = index_sequence<sizeof... (Cs)>; 
    // ugly fold way
    auto val = ((weight/=10,(int)(Cs-'0')*weight) + ...);
    return val;
}


int main(){
    std::cout << 0_c  << std::endl;
    std::cout << 00_c << std::endl;
    std::cout << 01_c << std::endl;
    std::cout << 123_c << std::endl;
}

This code works for simple cases(correctness is not important, e.g. negative numbers), it is just an example, but code looks ugly and clang emits a warning for modifying weight multiple times, so I guess code is buggy(undefined or unspecified behavior) although it seems to work...

Now I wonder is there a way for me to transform the ints I use(it is from boost::mp11, but same thing exists in std::) with a stateful lambda (that modifies weight). So I would like to transfer ints, that are <0,1,2> into something like <100,10,1>

I presume this has been asked before but this is very hard to search for.

To be clear: operator "" is just a toy problem, my real question is about mapping the values of integer sequence with a stateful lambda.

Also if not clear from question: I am perfectly happy to use boost mp11, but could not find anything in the docs.


Solution

  • So I would like to transfer ints, that are <0,1,2> into something like <100,10,1>

    First, you can convert std::index_sequence to std::array, then perform your operations on it as you normally do, and finally, convert std::array to std::index_sequence again.

    In order for the stateful lambda to work at compile-time, we can accept a function that can return the stateful lambda then get it internally:

    template<std::size_t... Is>
    constexpr auto transform_seq(std::index_sequence<Is...>, auto get_op) {
      // index_sequence -> array
      constexpr auto arr = [op = get_op()]() mutable {
        std::array<std::size_t, sizeof...(Is)> arr{Is...};
        for (auto& value : arr)
          value = op(value);
        return arr;
      }();
    
      // array -> index_sequence
      constexpr auto seq = [&]<std::size_t... Js>(std::index_sequence<Js...>) {
        return std::index_sequence<std::get<Js>(arr)...>{};
      }(std::make_index_sequence<arr.size()>{});
    
      return seq;
    };
    

    Then you can perform the index_sequence conversion according to op you pass in:

    using input1 = std::index_sequence<0,1,2>;
    auto gen_op1 = [] { 
      return [w = 1000](auto x) mutable { w /= 10; return w; }; 
    };
    using res1 = decltype(transform_seq(input1{}, gen_op1));
    static_assert(std::same_as<res1, std::index_sequence<100, 10, 1>>);
    
    using input2 = std::index_sequence<0,1,2,3>;
    auto gen_op2 = [] { 
      return [b = true] (auto x) mutable { b = !b; return b * 10 + x; }; 
    };
    using res2 = decltype(transform_seq(input2{}, gen_op2));
    static_assert(std::same_as<res2, std::index_sequence<0,11,2,13>>);
    

    Demo.