Boost Hana Compile-Time List Transformation - c++

I'm trying to figure out how to transform a list of integer constants at compile time using boost:hana.
I have my list as:
constexpr auto vals = hana::to<hana::tuple_tag>(hana::range_c<int, 0, 3>);
I want to apply the function:
constexpr auto Pow2(int i) { return 1 << i; }
However
constexpr auto res = hana::transform(list, Pow2);
produces a type for res of hana::tuple<int, int, int>. I'm not seeing how to move the argument to the lambda into a template argument for hana::int_c
// Compiler error: Non-type template argument is not a constant expression
constexpr auto Pow2(int i)
{
return hana::int_c<1 << i>{};
}

In ...
constexpr auto Pow2(int i) { return 1 << i; }
...i is a runtime integer. It is not a "compile-time-friendly" parameter, as its value is not stored as part of its type. You should pass in a int_ instead:
template <int X>
constexpr auto Pow2(hana::int_<X>) { return hana::int_c<1 << X>; }
Usage:
constexpr auto vals = hana::to<hana::tuple_tag>(hana::range_c<int, 0, 3>);
constexpr auto res = hana::transform(vals, [](auto x){ return Pow2(x); });
static_assert(std::is_same_v<
std::decay_t<decltype(res)>,
hana::tuple<hana::int_<1>, hana::int_<2>, hana::int_<4>>
>);
wandbox example
Obviously, you can also do this with a lambda. Additionally, boost::hana::int_ has an operator<< overload that returns an int_:
hana::transform(vals, [](auto x){ return hana::int_c<1> << x; });
wandbox example

Related

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.
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.
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;
}

How to remove redundancy from and add flexibility to template parameters?

I want to use constexpr, compile time generated std::arrays for fast value-lookup instead of lengthy runtime calculations. For that I drafted a templated constexprfunction that will be executed at compile time.
Please see the following example code, which allows for ultrafast access to Triangle and Fibonacci numbers and factorials.
#include <iostream>
#include <utility>
#include <array>
constexpr size_t ArraySize = 20u;
// Some generator functions -------------------------------------------------------------
constexpr size_t getTriangleNumber(size_t row) noexcept {
size_t sum{};
for (size_t i{ 1u }; i <= row; i++) sum += i;
return sum;
}
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
constexpr unsigned long long getFactorial(size_t index) noexcept {
unsigned long long result{ 1 };
while (index > 0) { result *= index; --index; }
return result;
}
// Generate a std::array with n elements of a given type and a generator function -------
template <typename DataType, DataType(*generator)(size_t), size_t... ManyIndices>
constexpr auto generateArray(std::integer_sequence<size_t, ManyIndices...>) noexcept {
return std::array<DataType, sizeof...(ManyIndices)>{ { generator(ManyIndices)... } };
}
// The arrays ---------------------------------------------------------------------------
constexpr auto TriangleNumber = generateArray<size_t, getTriangleNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto FibonacciNumber = generateArray<unsigned long long, getFibonacciNumber>(std::make_integer_sequence<size_t, ArraySize>());
constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());
// Some debug test driver code
int main() {
for (const auto t : TriangleNumber) std::cout << t << ' '; std::cout << '\n';
for (const auto f : FibonacciNumber) std::cout << f << ' '; std::cout << '\n';
for (const auto f : Factorial) std::cout << f << ' '; std::cout << '\n';
return 0;
}
As you can see. The template uses a parameter "DataType". In my opinion this is redundant. This is always the return type of the generator function. And it will also determine the data type for the std::array
So, how can we eliminate this redundancy and just use the type given by the generator function?
Additionally. The functions parameter is always size_t. There is also a redundancy and it is also not very flexible. The type of "ManyIndices" and the function parameter are always the same. So, no need to write that double.
Regarding flexibility. If I want to use a generator function with a different parameter data type, say, unsigned long long as in
constexpr unsigned long long factorial(unsigned long long n) noexcept {
return n == 0ull ? 1ull : n * factorial(n - 1ull);
}
I cannot do that. So, basically everything should be deduced from the generators functions signature.
This is also valid for the lines like
constexpr auto Factorial = generateArray<unsigned long long, getFactorial>(std::make_integer_sequence<size_t, ArraySize>());
Here, size_t is also the type of the parameter of the given function.
So, how eliminate redundancy and add flexibility?
DataType can be deduced from passed generator, use std::declval.
std::integer_sequence can be replaced by std::index_sequence.
Size for calculation must be provided explicitly.
template <typename GEN, size_t ... Indices>
constexpr auto generateArray2Helper(GEN gen, std::index_sequence<Indices...>) {
return std::array<decltype(std::declval<GEN>()(size_t{})), sizeof...(Indices)>{ gen(Indices)... };
}
template <size_t N, typename GEN>
constexpr auto generateArray2(GEN gen) {
return generateArray2Helper(gen, std::make_index_sequence<N>());
}
// The arrays ---------------------------------------------------------------------------
constexpr auto TriangleNumber = generateArray2<ArraySize>(getTriangleNumber);
constexpr auto FibonacciNumber = generateArray2<ArraySize>(getFibonacciNumber);
constexpr auto Factorial = generateArray2<ArraySize>(getFactorial);
Demo
c++20 version:
template<std::size_t...Is>
constexpr auto index_over(auto f, std::index_sequence<Is...>){
return f(std::integral_constant<std::size_t,Is>{}...);
}
template<auto N>
constexpr auto index_upto(auto f){
return index_over(f, std::make_index_sequence<N>{});
}
template<auto size>
constexpr auto gen_array(auto f){
return index_upto<size>([&](auto...Is){
return std::array{f(Is)...};
});
}

Can I change the template argument deduction order for a generic variadic lambda?

Take the following code, which is a simplified example:
template <typename F>
void foo(F f) {
//bool some = is_variadic_v<F>; // Scenario #1
bool some = true; // Scenario #2
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
A function takes a generic variadic lambda and calls it with a fixed set of arguments. This lambda itself then just calls another function/lambda with a matching prototype.
As one could expect, in scenario 2, when f is called inside foo, the compiler will deduce params... to be the parameter pack {1, 1}.
For scenario #1, I am using a code from another Q&A to deduce the arity of a callable object. If however such an object is callable with more than a pre-defined maximum amount of arguments, it is considered "variadic". In detail, is_variadic_v will employ a form of expression SFINAE where it is attempted to call the function object with a decreasing number of arguments having an "arbitrary type" that is implictly convertible to anything.
The problem is now that apparently, the compiler will deduce F (and along its argument pack) during this metacode, and if it is variadic (such as in this case), it deduces F as a lambda taking the dummy arguments, i.e. something like main()::lambda(<arbitrary_type<0>, arbitrary_type<1>, arbitrary_type<2>, ..., arbitrary_type<N>>) if N is the "variadic limit" from above. Now params... is deduced as arbitrary_type<1>, arbitrary_type<2>, ... and correspondingly, the call some(params...) will fail.
This behaviour can be demonstrated in this little code example:
#include <utility>
#include <type_traits>
#include <iostream>
constexpr int max_arity = 12; // if a function takes more arguments than that, it will be considered variadic
struct variadic_type { };
// it is templated, to be able to create a
// "sequence" of arbitrary_t's of given size and
// hence, to 'simulate' an arbitrary function signature.
template <auto>
struct arbitrary_type {
// this type casts implicitly to anything,
// thus, it can represent an arbitrary type.
template <typename T>
operator T&&();
template <typename T>
operator T&();
};
template <
typename F, auto ...Ints,
typename = decltype(std::declval<F>()(arbitrary_type<Ints>{ }...))
>
constexpr auto test_signature(std::index_sequence<Ints...> s) {
return std::integral_constant<int, size(s)>{ };
}
template <auto I, typename F>
constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{ })) {
return { };
}
template <auto I, typename F, typename = std::enable_if_t<(I > 0)>>
constexpr auto arity_impl(...) {
// try the int overload which will only work,
// if F takes I-1 arguments. Otherwise this
// overload will be selected and we'll try it
// with one element less.
return arity_impl<I - 1, F>(0);
}
template <typename F, auto MaxArity>
constexpr auto arity_impl() {
// start checking function signatures with max_arity + 1 elements
constexpr auto tmp = arity_impl<MaxArity+1, F>(0);
if constexpr (tmp == MaxArity+1)
return variadic_type{ }; // if that works, F is considered variadic
else return tmp; // if not, tmp will be the correct arity of F
}
template <typename F, auto MaxArity = max_arity>
constexpr auto arity(F&&) { return arity_impl<std::decay_t<F>, MaxArity>(); }
template <typename F, auto MaxArity = max_arity>
constexpr auto arity_v = arity_impl<std::decay_t<F>, MaxArity>();
template <typename F, auto MaxArity = max_arity>
constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_type>;
template <typename F>
void foo(F f) {
bool some = is_variadic_v<F>;
//bool some = true;
f(int(some), int(some));
}
int main() {
auto some = [](int i, int j) {
std::cout << i << " " << j << '\n';
};
foo([&some](auto... params) {
some(params...);
});
}
Can I prevent this behaviour? Can I force the compiler to re-deduce the parameter list?
EDIT:
An additional peculiarity is that the compiler seems to act kind of schizophrenic. When I change the contents of foo to
foo([&some](auto... params) {
// int foo = std::index_sequence<sizeof...(params)>{ };
std::cout << sizeof...(params) << '\n';
});
the compiler will create a program that will print 2 in this example. If however I include the commented line (which, as it makes no sense, should trigger a compiler diagnostic), I get confronted with
error: cannot convert 'std::index_sequence<13>' {aka 'std::integer_sequence<long unsigned int, 13>'} to 'int' in initialization
85 | int foo = std::index_sequence<sizeof...(params)>{ };
so does the compiler now deduces sizeof...(params) to be 2 and 13 at the same time? Or did he change his mind and chooses now 13 just because I added another statement into the lambda? Compilation will also fail if I instead choose a static_assert(2 == sizeof...(params));. So the compiler deduces sizeof...(params) == 2, except if I ask him whether he did deduce 2, because then he didn't.
Apparently, it is very decisive for the parameter pack deduction what is written inside the lambda. Is it just me or does this behaviour really look pathologic?

How to remove metaprogramming recursion with Boost Hana

I'm trying to create a bitset according to the type send to the function. But let's reduce the test case a little.
Warning : I'm using auto gcc extension for this example, I don't need to use template parameter.
namespace hana = boost::hana;
constexpr decltype(auto) rec(auto i, auto max, auto f, auto returnValue) {
return returnValue |= f(i);
if constexpr (i < max) //"infinite" loop if no constexpr
return rec(i + hana::size_c<1>, max, f, returnValue);
else
return returnValue;
}
constexpr decltype(auto) foo(auto ct, auto ... type) {
constexpr auto tuple = hana::make_tuple(type...);
constexpr unsigned long returnValue = 0L;
constexpr auto f = [tuple, ct] (auto i) {
if (hana::contains(tuple, ct[i]))
return 0 << decltype(i)::value;
else
return 0;
};
return rec(hana::size_c<0>, hana::size_c<3>, f, returnValue);
}
struct T1 {};
struct T2 {};
struct T3 {};
int main () {
constexpr auto t1 = hana::type_c<T1>;
constexpr auto t2 = hana::type_c<T2>;
constexpr auto t3 = hana::type_c<T3>;
constexpr auto ct = hana::make_tuple(t1, t2, t3);
constexpr auto test = foo(ct, t1, t2);
}
Seems like my tuple is not considered Searchable, but if I try the same hana::contains outside the lambda I got no problem.
The whole error is huge so check it there : live demo
By the way, I tried to do this with a for loop but failed. Do you know a good way of doing this kind of things in C++17/20 ?
The error is caused by an out of bounds access caused by the use of manual recursion. Part of the purpose of functional programming is to provide constructs to eliminate the possibility of these kinds of mistakes.
Here are a few examples, but it is recommended to take a look at the manual for the concept hana::Foldable as it is really foundational in using Boost.Hana.
hana::fold_left hides the recursion for you and can reduce the amount of recursive calls via fast-tracking:
constexpr decltype(auto) foo = [](auto ct, auto ... type) {
constexpr auto tuple = hana::make_tuple(type...);
return hana::fold_left(hana::make_range(hana::size_c<0>, hana::size_c<3>), 0L,
[tuple, ct](auto returnValue, auto i)
{
// returnValue param is not constexpr
if (hana::contains(tuple, ct[i])) {
return returnValue | (1 << decltype(i)::value);
}
else
{
return returnValue;
}
}
);
};
hana::fold_left example
hana::unpack eliminates recursion altogether using variadic pack expansion:
constexpr decltype(auto) foo = [](auto ct, auto ... type) {
constexpr auto tuple = hana::make_tuple(type...);
auto f = [tuple, ct](auto i)
{
return hana::contains(tuple, ct[i]) ? (1 << decltype(i)::value) : 0;
};
return hana::unpack(hana::make_range(hana::size_c<0>, hana::size_c<3>),
[f](auto ...i) { return (f(i) | ...); }
);
};
hana::unpack example

lambda calling another external lambda

I can easily do this:
auto f = []()->int { return 4; };
auto g = [f]()->int { return f(); });
int i = g();
Nevertheless, I cannot do this:
int (*f)() = []()->int { return 4; };
int (*g)() = [f]()->int { return f(); });
int i = g();
Why I got such message in MSVC?
error C2440: 'initializing' : cannot convert from 'ClassName::functionName::< lambda_b2eebcdf2b88a20d8b40b0a03c412089>' to 'int (__cdecl *)(void)'
This occurs on line:
int (*g)() = [f]()->int { return f(); });
How to do this properly?
int (*f)() = []()->int { return 4; };
is still fine because lambdas with empty capture lists implicitly convert to matching function pointers.
This (crucial) condition is however not met in the second line:
int (*g)() = [f]()->int { return f(); });
^
Thus the conversion fails.
If you want to store a lambda that captures something, you either need an std::function, or deduce the type with auto as you did before; whatever fits your usecase. Function pointers simply cannot do that (in C++11, for the future, see Yakk's answer).
Well, you can wait for C++17.
template<auto F>
struct function_ptr;
template<class R, class...Args, R(*F)(Args...)>
struct function_ptr<F> {
using signature = R(Args...);
constexpr R operator()(Args...args)const {
return F(std::forward<Args>(args)...);
}
constexpr operator signature*() const { return F; }
constexpr signature* operator+() const { return F; }
};
now:
constexpr auto f_ = []()->int { return 4; };
function_ptr<+f_> f;
generates a function-pointer like f.
template<class T>struct tag_t {};
template<class F, class...Fs, class R, class...Args>
constexpr auto chain_functions(tag_t<R(Args...)>) {
constexpr r = [](Args...args)->R{
return F{}( Fs{}..., std::forward<Args>(args)... );
};
return function_ptr<+r>{};
}
lets us chain function pointers.
constexpr auto f_ = []()->int { return 4; };
function_ptr<+f_> f0;
constexpr auto g_ = [](int(*f)())->int { return f(); });
function_ptr<+g_> g_raw;
auto g0 = chain_functions< function_ptr<+g_>, function_ptr<+f_> >( tag_t<int()>{} );
now g is a function_ptr.
int(*g)() = g0;
should hopefully compile and work. (Not tested, I don't have access to a sufficiently C++17 compiler).
Still a bit obtuse, and definitely not tested. Basically function_ptr is intended to create a type that carries a compile-time function pointer. C++17 provides us with constexpr lambdas, including the ability to get the function pointer out of them in a constexpr context.
We can then compose these function pointer types to generate a new function pointer type.