c++ - constexpr 可变参数模板和解包 std::array

标签 c++ c++11 variadic-templates constexpr

我想编写一个 constexpr 模板函数来置换作为参数传入的数组元素。所以我想出了这样的事情:

template <typename T, std::size_t N, typename... Ts>
constexpr std::array<T, N> permute(const std::array<T, N>& arr, const std::array<int, N>& permutation, Ts&&... processed)
{
    return (sizeof...(Ts) == N) ?
        std::array<T, N>{ std::forward<Ts>(processed)... } :
        permute(arr, permutation, std::forward<Ts>(processed)..., arr[permutation[sizeof...(Ts)]]);
}

使用示例:

constexpr std::array<int, 3> arr{ 1, 2, 3 };
constexpr std::array<int, 3> permutation{ 2, 1, 0 };
constexpr auto result = permute(arr, permutation); //result should contain { 3, 2, 1 }

问题是上面的代码无法编译。出于某种原因,g++ 6.4 尝试使用隐藏在'processed' 模板参数包下的 4 个或更多参数来实例化置换模板。 你能帮我更正我的代码并使其通过编译吗?

Full code

最佳答案

我将提供一个“快速修复”来演示问题的原因,然后展示如何在 C++11 中解决该问题。之后,我将展示如何使用较新的功能(C++14 及更高版本)来获得更简单的实现。


诊断

编译失控的原因是编译器必须生成条件的两个分支并检查它们的正确性,即使它可能证明其中一个分支永远不会被评估。

在较新版本的 C++ 中,我们可以通过将 ? 替换为 if constexpr 来使其工作:

#include <array>
#include <cstddef>
#include <utility>

template <typename T, std::size_t N, typename... Ts>
constexpr std::array<T, N> permute(const std::array<T, N>& arr,
                                   const std::array<int, N>& permutation,
                                   Ts&&... processed)
{
    if constexpr (sizeof...(Ts) == N)
        return std::array<T, N>{ std::forward<Ts>(processed)... };
    else
        return permute(arr, permutation, std::forward<Ts>(processed)...,
                       arr[permutation[sizeof...(Ts)]]);
}

int main()
{
    constexpr std::array<int, 3> arr{ 1, 2, 3 };
    constexpr std::array<int, 3> permutation{ 2, 1, 0 };
    constexpr auto result = permute(arr, permutation);

    return result != std::array<int, 3>{ 3, 2, 1 };
}

(对于这些较新版本的 C++,这可以使用 std::index_sequence 进一步简化,我稍后会展示)。


C++11代码

C++11 没有if constexpr,所以我们需要改用 SFINAE:

#include <array>
#include <cstddef>
#include <utility>

template <typename T, std::size_t N, typename... Ts>
constexpr typename std::enable_if<sizeof...(Ts) == N, std::array<T, N> >::type
permute(const std::array<T, N>&, const std::array<int, N>&,
        Ts&&... processed)
{
    return std::array<T, N>{ std::forward<Ts>(processed)... };
}

template <typename T, std::size_t N, typename... Ts>
constexpr typename std::enable_if<sizeof...(Ts) != N, std::array<T, N> >::type
permute(const std::array<T, N>& arr, const std::array<int, N>& permutation,
        Ts&&... processed)
{
    return permute(arr, permutation, std::forward<Ts>(processed)...,
                   arr[permutation[sizeof...(Ts)]]);
}

在这里,我们为 sizeof...(Ts) == Nsizeof...(Ts) != N 提供完全独立的函数,并使用 std::enable_if 在它们之间进行选择。


C++14 以上

如果我们能够使用 C++14 或更高版本,我们将获得 std::index_sequence,这大大简化了对数组或元组的所有元素的操作。这仍然需要两个函数,但这次它们中的一个调用另一个,逻辑更容易理解:

#include <array>
#include <cstddef>
#include <utility>

template<typename T, std::size_t N, std::size_t... I>
constexpr std::array<T, N>
permute_impl(const std::array<T, N>& a, const std::array<int, N>& p,
             std::index_sequence<I...>)
{
    return { a[p[I]]... };
}


template<typename T, std::size_t N, typename I = std::make_index_sequence<N>>
constexpr std::array<T, N>
permute(const std::array<T, N>& a, const std::array<int, N>& p)
{
    return permute_impl(a, p, I{});
}

如果您不止一次需要它并且您只能使用 C++11,那么实现您自己的 index_sequence 甚至可能是值得的。

关于c++ - constexpr 可变参数模板和解包 std::array,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50471075/

相关文章:

C++,linux,如何从字符串中有效地 pop_back() 一个非 latin1 字符

c++ - 构造函数中的重载函数导致引用错误

c++ - 使用 ASCII 值确定 A 是否是 B 的排列

c++ - 使用平面数组存储的元组容器

c++ - ifstream 读取所有以开头的文件

c++ - 如何删除 QStandardItemModel 中的垂直标题?

c++ - 而在std::cout , what does it work for?之前使用左移运算符(<<)

c++ - C++11 中的非类型可变参数函数模板

C++20 如何获取 std::size_t 参数包的最后一个元素

c++ - 在 Qt 中为整个小部件设置样式表