Is there a way to pass a `constexpr` value into lambda so that it remains `constexpr` inside that lambda?
Asked Answered
G

2

6

Here's what I want to do; posting the whole code because it's not too long and also to demonstrate the specific task I'm trying to solve. Basically, I need a way to iterate values from parameter pack by index (the index part is important, even though it's not required in this example).

#include <iostream>
#include <tuple>
#include <type_traits>

template <int First, int Last, typename Functor>
constexpr void static_for(Functor&& f)
{
    if constexpr (First < Last)
    {
        f(std::integral_constant<int, First>{});
        static_for<First + 1, Last, Functor>(std::forward<Functor>(f));
    }
}

template <size_t index, typename... Args>
auto value_by_index(Args&&... args) noexcept {
    return std::get<index>(std::forward_as_tuple(std::forward<Args>(args)...));
}

template <typename... ValueTypes>
void traverse(ValueTypes... values)
{
    static_for<0, sizeof...(ValueTypes)>([&](int i) {
        auto v = value_by_index<static_cast<size_t>(i), ValueTypes...>(values...);
        std::cout << v << std::endl;
    });
}

int main()
{
    traverse(0.0f, 1, 3.33, "str");

    return 0;
}

The compiler error, of course, is:

<source>:24:71: error: 'i' is not a constant expression

If lambdas could have explicit template arguments, i would be such an argument and it would be obvious to the compiler that it's known at compile time. But that's not how lambdas work.

If you want to treat it as an X-Y problem, I suppose I don't specifically need to call a lambda inside my static_for, but I do need to call some piece of code that can access parameter pack(s) of traverse by index, and if traverse was a member function, I need to have access to its this.

Try it online: https://godbolt.org/z/eW4rnm

Gigantopithecus answered 28/11, 2018 at 15:22 Comment(0)
Q
7

Use a generic lambda and a constexpr conversion operator :

template <typename... ValueTypes>
void traverse(ValueTypes... values)
{
    static_for<0, sizeof...(ValueTypes)>([&](auto I)
    //                                       ~~~^
    {
        auto v = value_by_index<I>(values...);
        //                     ~^~
        std::cout << v << std::endl;
    });
}

DEMO

Use a template parameter list for the lambda expression :

template <typename... ValueTypes>
void traverse(ValueTypes... values)
{
    static_for<0, sizeof...(ValueTypes)>([&]<int I>(std::integral_constant<int, I>)
    //                                       ~~~~^                             ~^~
    {
        auto v = value_by_index<I>(values...);
        //                     ~^~
        std::cout << v << std::endl;
    });
}

DEMO 2

Quicklime answered 28/11, 2018 at 15:28 Comment(5)
Wow, such a silly mistake on my part! It seems that the type of i was integral_constant, not int?Gigantopithecus
@VioletGiraffe yes, and the type itself is known at compile-time. see that I'm not using the value of iQuicklime
Since std::integral_constant<int, I> is convertible to int, and the conversion function is constexpr, it should be possible to use i in place of decltype(i)::value.Belomancy
Well compilers accept value_by_index<i>(values...), and that makes sense since the conversion function does not need to read the value of i.Belomancy
At first I didn't understand, but now I see what you did there. You've converted the value (int) into a template type holding that value as a non-type template parameter. std::integral_constant conveniently provides such a wrapper for integer types; a custom holder template could be written for values of other kinds. A lambda doesn't see its value argument as constexpr, but it does see the specific type it's invoked with, and there's constexpr conversion from the type to the value it holds. Clever.Gigantopithecus
K
4

It's too late to play?

Basically, I need a way to iterate values from parameter pack by index (the index part is important, even though it's not required in this example).

Sorry but... what about the good old use of std::make_index_sequence and std::index_sequence ?

Maintaining your value_by_index(), I propose the following C++14 solution based on traverse() with traverse_helper()

template <typename F, std::size_t ... Is, typename ... VTs>
void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
 { 
   using unused = int[];

   (void)unused { 0, (f(value_by_index<Is>(vs...)), 0)... };
 }

template <typename F, typename ... VTs>
void traverse (F f, VTs ... vs)
 { traverse_helper(f, std::make_index_sequence<sizeof...(VTs)>{}, vs...); }

Observe that I've passed also the callable as parameter.

If you can use C++17 (as you tagged), traverse_helper() simply become

template <typename F, std::size_t ... Is, typename ... VTs>
void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
 { (f(value_by_index<Is>(vs...)), ...); }

You can call traverse() as follows

traverse([](auto x){ std::cout << x << std::endl; },
         0.0f, 1, 3.33, "str");

The following is a full C++14 compiling example

#include <iostream>
#include <tuple>
#include <type_traits>

template <std::size_t I, typename ... As>
auto value_by_index (As && ... as) noexcept
 { return std::get<I>(std::forward_as_tuple(std::forward<As>(as)...)); }

template <typename F, std::size_t ... Is, typename ... VTs>
void traverse_helper (F f, std::index_sequence<Is...>, VTs ... vs)
 { 
   using unused = int[];

   (void)unused { 0, (f(value_by_index<Is>(vs...)), 0)... };
 }

template <typename F, typename ... VTs>
void traverse (F f, VTs ... vs)
 { traverse_helper(f, std::make_index_sequence<sizeof...(VTs)>{}, vs...); }

int main ()
 {
    traverse([](auto x){ std::cout << x << std::endl; },
             0.0f, 1, 3.33, "str");
 }
Kano answered 28/11, 2018 at 18:36 Comment(2)
Interesting! I have to admit, I don't understand what index_sequence does and what it's for. Going to read on it.Gigantopithecus
@VioletGiraffe - std::index_sequence, and std::make_index_sequence that permit to generate sequences, are a very useful tool to works with variadic templates.Kano

© 2022 - 2024 — McMap. All rights reserved.