如何从循环中生成的索引传递模板参数?

sh1*_*sh1 3 c++ templates index-sequence

假设我想在循环中一遍又一遍地调用一些模板,并在模板参数中使用循环索引。像这样(除非不违法):

template <typename T, int k>
T function(T x) {
    for (int i = 1; i <= k; ++i) {
        for (int j = i - 1; j >= 0; --j) {
            constexpr bool method_one_ok = function_of(i, j);
            if (method_one_ok) {
                x = method_one<T, i, j, k>(x);
            } else {
                x = method_two<T, i, j, k>(x);
            }
        }
    }
    return x;
}
Run Code Online (Sandbox Code Playgroud)

我知道如何使用递归模板和专业化来艰难地完成它,但这是我在 C++11 或更早版本中发现的恐怖之处。

有没有更干净的方法来做到这一点?

Art*_*yer 5

这是一个方便的功能:

template<typename T, T... I, typename F>
constexpr void idx_for_each(F&& f, std::integer_sequence<T, I...>) {
    (static_cast<void>(f(std::integral_constant<T, I>{})), ...);
}
Run Code Online (Sandbox Code Playgroud)

它将调用一个函数,其中每个整数都integer_sequence包含在integral_constant. 例如:

idx_for_each(f, std::make_index_sequence<3>{})
// Calls
f(std::integral_constant<std::size_t, 0>{});
f(std::integral_constant<std::size_t, 1>{});
f(std::integral_constant<std::size_t, 2>{});
Run Code Online (Sandbox Code Playgroud)

然后通过一些巧妙的转换 到0 -> X您想要的范围1 -> k+1i-1 -> -1,您可以编写:

template <typename T, int k>
T function(T x) {
    idx_for_each([&](auto ii) {
        constexpr int i = ii + 1; 
        idx_for_each([&](auto jj) {
            constexpr int j = i - jj - 1;
            constexpr bool method_one_ok = function_of(i, j);
            if (method_one_ok) {
                x = method_one<T, i, j, k>(x);
            } else {
                x = method_two<T, i, j, k>(x);
            }
        }, std::make_integer_sequence<int, i>{});
    }, std::make_integer_sequence<int, k>{});
    return x;
}
Run Code Online (Sandbox Code Playgroud)

通过替换折叠表达式,可以使其在 C++14 中工作:

template<typename T, T... I, typename F>
constexpr void idx_for_each(F f, std::integer_sequence<T, I...>) {
    // (static_cast<void>(f(std::integral_constant<T, I>{})), ...);
    using consume = int[];
    static_cast<void>(consume{ (static_cast<void>(f(std::integral_constant<T, I>{})), 0)... });
}
Run Code Online (Sandbox Code Playgroud)

并且可以通过为 C++11 实现std::integer_sequence/std::make_integer_sequence来使其成为 C++11。

您可以通过使用一个range<start, stop>直接从开始到停止的整数序列的帮助器来使这对于您的特定情况变得更容易,这样您就不必操纵函数参数。


一般来说,“模板循环”是通过创建一个新包并将std::index_sequence其应用于函数,然后折叠该包来实现的。

C++20 解决方案是完全按照带有模板参数列表的 lambda 来完成此操作:

template <typename T, int k>
T function(T x) {
    [&]<int... ii>(std::integer_sequence<int, ii...>) {
        ([&]{
            constexpr int i = ii + 1;
            [&]<int... jj>(std::integer_sequence<int, jj...>) {
                ([&]{
                    constexpr int j = i - jj - 1;
                    constexpr bool method_one_ok = function_of(i, j);
                    if constexpr (method_one_ok) {
                        x = method_one<T, i, j, k>(x);
                    } else {
                        x = method_two<T, i, j, k>(x);
                    }
                }, ...);
            }(std::make_integer_sequence<int, i>{});
        }(), ...);
    }(std::make_integer_sequence<int, k>{});
    return x;
}
Run Code Online (Sandbox Code Playgroud)