2
#include <iostream>

#include <tuple>
#include <iostream>
#include <utility>

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
}

template <std::size_t N>
auto make_index_dispatcher() 
{
    return make_index_dispatcher(std::make_index_sequence<N>{});
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t](auto idx) { f(std::get<idx>(std::forward<Tuple>(t))); });
}

int main() 
{
    for_each(std::make_tuple(1, 42.1, "hi"), [](auto&& e) {std::cout << e << ","; });
}

Question 1> Why I have to use std::integral_constant<std::size_t, Idx>{} instead of simply Idx in the following statement? Based on my understanding, std::integral_constant<std::size_t, Idx> is a type. Is it true that std::integral_constant<std::size_t, Idx>{} is a value of Idx?

// OK

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(std::integral_constant<std::size_t, Idx>{}), ...); };
}

// Error

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f(Idx), ...); };
}

Is it true that std::get expected compile-time constant expression while Idx is NOT a compile-time constant expression?

Question 2> Why we cannot pass std::index_sequence by reference?

// Error: auto make_index_dispatcher(std::index_sequence<Idx...>&)

Thank you

max66
  • 65,235
  • 10
  • 71
  • 111
q0987
  • 34,938
  • 69
  • 242
  • 387

3 Answers3

6

Why I have to use std::integral_constant{} instead of simply Idx in the following statement?

Because function arguments are never constant expressions. You can simply pass the indices as non-type template parameters instead, which are constant expressions. This works especially well with a C++20 template lambda:

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f.template operator()<Idx>(), ...); };
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t]<auto Idx>(){ f(std::get<Idx>(std::forward<Tuple>(t))); });
}

live example on godbolt.org


Why we cannot pass std::index_sequence by reference?

You can, but you need to invoke your function with an lvalue like with any other non-const reference. This compiles:

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>&) 
{
}

int main()
{
    std::index_sequence<> is;        
    make_index_dispatcher(is);
}

Also, it's completely useless.


Also, your entire code can simply be:

int main() 
{
    std::apply([](auto&&... xs)
    {
        ((std::cout << xs << ','), ...);
    }, std::make_tuple(1, 42.1, "hi"));
}
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I tried your code in the first section and it doesn't pass compiler error C2760: syntax error: unexpected token ')', expected 'expression'. I am using VS2017 v15.8.8 – q0987 Aug 09 '19 at 14:15
4

Why I have to use std::integral_constant{} instead of simply Idx in the following statement?

Is it true that std::get expected compile-time constant expression while Idx is NOT a compile-time constant expression?

std::get<> expects a compile-time expression, but you are not actually using idx directly (which is not a compile-time expression).

std::integral_constant<std::size_t, I> has a convenient constexpr conversion operator to std::size_t which returns I, so when you do:

std::get<idx>(...)

...you are actually doing std::get<(std::size_t)idx>, and (std::size_t)idx is a compile-time expression since the conversion operator is constexpr.

If you do not wrap Idx inside a std::integral_constant, the type of idx in the generic lambda will be std::size_t, and all the above will not work.

Holt
  • 36,600
  • 7
  • 92
  • 139
2

Why I have to use std::integral_constant{} instead of simply Idx in the following statement?

Just for fun...

What you say is true (as far I known) until C++17 (because the std::get() template argument must be known compile time and the use of std::integral_constant is an elegant way to pass a compile-time-known constant as type of an auto parameter in a generic (C++14 or later) lambda; see other answers for clearer explanations) but not true anymore (not necessarily you have to use std::integral_constant) starting from C++20.

In fact, C++20 introduces template lambdas, so you can rewrite your code as follows, passing the Idx values as template parameters and without std::integral_constant

#include <tuple>
#include <iostream>
#include <utility>

template <std::size_t... Idx>
auto make_index_dispatcher(std::index_sequence<Idx...>) 
{
    return [](auto&& f) { (f.template operator()<Idx>(), ...); };
} // ......................^^^^^^^^^^^^^^^^^^^^^^^^^^^^   modified lambda call                        

template <std::size_t N>
auto make_index_dispatcher() 
{
    return make_index_dispatcher(std::make_index_sequence<N>{});
}

template <typename Tuple, typename Func>
void for_each(Tuple&& t, Func&& f) 
{
    constexpr auto n = std::tuple_size<std::decay_t<Tuple>>::value;
    auto dispatcher = make_index_dispatcher<n>();
    dispatcher([&f, &t]<std::size_t I>() { f(std::get<I>(std::forward<Tuple>(t))); });
} // ..................^^^^^^^^^^^^^^^^^ template parameter added, argument removed

int main() 
{
    for_each(std::make_tuple(1, 42.1, "hi"), [](auto&& e) {std::cout << e << ","; });
}
max66
  • 65,235
  • 10
  • 71
  • 111