1

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.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • Something like [this](https://godbolt.org/z/WjhKzKo9j)? – NathanOliver Oct 07 '21 at 15:13
  • 1
    Clang is objecting because the operands of `+` are unsequenced, so you can’t read and modify the same variable (or modify it twice) in them. – Davis Herring Oct 07 '21 at 15:17
  • Code is indeed buggy. You have order with comma operator for a single expression, buts order for different 'pack' is unordered (with `+`); All `weight/=10` can happens first, and the the sum. – Jarod42 Oct 07 '21 at 15:18
  • @NathanOliver I prefer to apply lambda to integer sequence, since this is just a toy example. – NoSenseEtAl Oct 07 '21 at 15:19
  • @NathanOliver: That doesn't solve the issues about order though. – Jarod42 Oct 07 '21 at 15:19
  • Regular loop might do the job BTW (you just need to wrap it in another constexpr function (or lambda) to allow to force to have constant expression `constexpr auto res = [&](){/**/}(); return res;`). – Jarod42 Oct 07 '21 at 15:22
  • You want to apply `pow(10, 2 - Is)` to `std::index_sequence<0, 1, 2>`? – Jarod42 Oct 07 '21 at 15:25
  • @Jarod42 I want a generic way to apply lambda to values in integer sequence, this is just a toy example, for example if lambda does add 0 or 10 in zig zag way using some state(boolean) (first 0, then 10, then 0) then it would map integer sequence <0,1,2,3,> to integer sequence<0,11,,2,13> – NoSenseEtAl Oct 07 '21 at 15:29
  • @Jarod42 Ah yes, `+` was not one of the operators that was updated in C++17 with better sequencing. – NathanOliver Oct 07 '21 at 15:33
  • @NathanOliver good point, thank you and Jarod42, I presumed that C++17 made it at least unspecified, and that warning is just outdated. :) – NoSenseEtAl Oct 07 '21 at 15:34
  • 1
    @NoSenseEtAl: Your version is UB, whereas the one from NathanOlivier might not give expected result, but is not UB. – Jarod42 Oct 07 '21 at 15:44
  • So you want a lambda that can transform `index_sequence` into another `index_sequence`? – 康桓瑋 Oct 07 '21 at 16:28
  • @康桓瑋 yes, but not necessarily a lambda, I am happy with some type function(struct) that accepts a lambda operating on integer and maps input integer sequence to integer sequence of "mapped" integer values, where mapping is some lambda that is stateful – NoSenseEtAl Oct 07 '21 at 16:30
  • @康桓瑋 not sure if stateful lambda can ever work within compile time, but that would be nice to have, so I can divide weight by 10 in each iteration – NoSenseEtAl Oct 07 '21 at 16:31

2 Answers2

2

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.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
1

I think you want:

template <typename F, std::size_t ... Is>
constexpr auto apply(F f, std::index_sequence<Is...>)
-> std::index_sequence<f(Is)...>
{
    return {};
}

template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){

    return []<std::size_t ... Pows>(std::index_sequence<Pows...>){
        return ((Pows * (Cs - '0')) + ...);
    }(apply([](std::size_t n){ return ipow(10, sizeof...(Cs) - n - 1);},
            std::make_index_sequence<sizeof...(Cs)>()));
}

Demo

But doing computation directly seems even simpler:

template <char... Cs>
[[nodiscard]] constexpr auto operator""_c(){
    constexpr auto res = 
        []<std::size_t ... Is>(std::index_sequence<Is...>){
            return ((ipow(10, sizeof...(Cs) - Is - 1) * (Cs - '0')) + ...);
        }(std::make_index_sequence<sizeof...(Cs)>());
    return res;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 2
    The interesting thing is that gcc [does not](https://godbolt.org/z/n153o4rjE) accept the syntax of the first method. – 康桓瑋 Oct 07 '21 at 16:06
  • 1
    @康桓瑋: `F{}(Is)...` instead of `f(Is)...` is ok for gcc [Demo](https://godbolt.org/z/bcb8q5oj3) (but we lost MSVC :/ ) – Jarod42 Oct 07 '21 at 16:11
  • Worked using aliases, but requires C++20 https://godbolt.org/z/dz5rdKnsT – 김선달 Oct 08 '21 at 01:56