2

Is it possible to have a lambda with a constexpr argument? And is it possible to make the following example work?

ForEach function provided below calls a given lambda 3 times with index 0, 1, 2:

template <class Func, std::size_t... index>
inline constexpr void ForEach(Func && f, std::index_sequence<index...>)
{
    (f(index), ...);
}

template <class Func>
inline constexpr void ForEach(Func && f)
{
    ForEach(f, std::make_index_sequence<3>());
}

so the following code

ForEach([](size_t index)
{
    std::cout << index << ' ' << std::endl;
});

outputs 0, 1, 2.

But the following code that tries to print tuple elements requires index to be a constexpr:

auto t = std::make_tuple(1, 2.0, std::string("abc"));

ForEach([&t](size_t index)
{
    std::cout << std::get<index>(t) << ' ' << std::endl;
});

and thus does not compile, see live example. Is it possible to make index constexpr somehow?

EDIT1: There is a working example where a lambda argument is used as a template argument:

void Set(Tuple& val, size_t index, Variant const& elem_v)
{
    mp_with_index<std::tuple_size_v<Tuple>>(
        index,
        [&](auto I){
            std::visit([&](auto const& alt){
                if constexpr (std::is_assignable_v<
                        std::tuple_element_t<Tuple, I>,
                        decltype(alt)>)
                {
                    std::get<I>(val) = alt;
                } else {
                    throw /* something */;
                }
            }, elem_v);
        });
}

why does this compile, but my sample code does not?

4
  • 2
    No. Function arguments can't be treated as constexpr (not sure whether C++20 changes that). Commented Jul 8, 2019 at 15:16
  • @Fureeish see EDIT1. Why does it compile? Commented Jul 8, 2019 at 15:20
  • 2
    @AlexeyStarinsky I is a std::integral_constant<std::size_t, (something)> which has a constexpr operator std::size_t() that returns that (something) Commented Jul 8, 2019 at 15:22
  • 1
    @Fureeish It did not. Commented Jul 8, 2019 at 15:25

1 Answer 1

9

In this:

ForEach([&t](size_t index)
{
    std::cout << std::get<index>(t) << ' ' << std::endl;
});

index is not a constant expression. It's just a variable. Function parameters are not constexpr.

But if we tweaked ForEach somewhat (to work the same way as the example of mine that you linked):

template <class Func, std::size_t... index>
inline constexpr void ForEach(Func && f, std::index_sequence<index...>)
{
    (f(std::integral_constant<std::size_t, index>()), ...);
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //    instead of just index
}

ForEach([&t](auto index)
{
    std::cout << std::get<index>(t) << ' ' << std::endl;
});

Then this works because index is no longer size_t but rather different instances of std::integral_constant<size_t, V> for various V. That type looks something like:

template<class T, T v>
struct integral_constant {
    static constexpr T value = v;
    typedef T value_type;
    typedef integral_constant type; // using injected-class-name
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; } //since c++14
};

Converting a std::integral_constant<size_t, V> to a size_t invokes the constepxr operator size_t(), which doesn't involve reading any state from this object itself (which is an empty type), hence it's allowed as a constant expression.

A different way of looking at it is that we're encoding the value in the type (which can be retrieved as a constant expression) rather than in the value (which cannot).

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.