有没有一种方法可以模板化一个函数,以便可以输入一个 N 维大括号包围初始值设定项列表?

Is there a way to template a function so that an N-dimensional brace-enclosure initializer list can be input?

我正在开发一个函数,它需要允许输入一个 N 维大括号括起来的初始化列表,并输出一个一维向量。例如,输入

std::vector<int> flat_array = flatten({{{1,2,3},{4,5,6}},{{7,8,9},{10,11,12}}});

会输出 [1,2,3,4,5,6,7,8,9,10,11,12]。我正在尝试通过以下方式递归地完成此操作。

template <typename T>
std::vector<int>
flattenVector(const std::vector<std::vector<T>> & vec)
{
  std::vector<T> new_vec(vec[0]);
  for (unsigned int i = 1; i < vec.size(); ++i)
  {
    new_vec.insert(new_vec.end(), vec[i].begin(), vec[i].end());
  }
  return flatten(new_vec);
}

std::vector<int>
flattenVector(const std::vector<int> & vec)
{
  return vec;
}

不幸的是,由于编译器无法推导模板参数,这将无法工作。有没有一种方法可以在不在函数声明中显式写入每个嵌套向量的情况下执行此操作?

{1,2,3} 没有类型,无法在 const std::vector<T> & 中推导出来,但它们可能与 std::initializer_list<T> 一起。

因此,您可以将调用更改为:

std::vector<int> flat_array =
    flatten(std::vector<std::vector<std::vector<int>>>{{{1,2,3},{4,5,6}},{{7,8,9},{10,11,12}}});

我所能做的就是避免你编写这些嵌套的 std::vector<std::vector<... 参数:

#include <vector>
#include <initializer_list>
#include <iostream>


template<typename Y, size_t N, typename T>
struct Flattener : public Flattener<Y, N-1, std::initializer_list<T>> {
    std::vector<Y> flatten(std::initializer_list<T> l) {
        std::vector<Y> vec;
        for (auto & e : l)
        {
            std::vector<Y> nvec = Flattener<Y, N, Y>{}.flatten(e); 
            vec.insert(end(vec), begin(nvec), end(nvec)); // Change this to a move, copies are no fun!
        }
        return vec;
    };
    using Flattener<Y, N-1, std::initializer_list<T>>::flatten;
};

template<typename Y, typename T>
struct Flattener<Y, 0, T> {
    std::vector<Y> flatten(Y y) { return {{y}}; };
};

int main()
{
    auto v = Flattener<int, 10, int>{}.flatten({{{1,2}, {3,4,5}}});
    for (auto e : v) std::cout << e << std::endl;
}

(ideone link)

Flattener<int, 10, int> 创建了一个 class,它有成员函数 flatten 和重载,可以将嵌套 std::initializer_list 的 10 层深度解压缩到一个 std::vector<int> 中。

如您所见,您需要了解最低级别 (int) 的类型和最大深度 (10)。


Flattener<int, 2, int>

  • 成员函数flatten(std::initializer_list<int>)
  • base class Flattener<int, 1, std::initializer_list<int>>its 成员函数。它有:
    • 成员函数flatten(std::initializer_list<std::initializer_list<int>>)
    • base class Flattener<int, 0, std::initializer_list<std::initializer_list<int>>>its 成员函数。它有:
      • 成员函数flatten(int)

因此,Flattener<int, 2, int> class 具有这些成员函数:

  • flatten(int)
  • flatten(std::initializer_list<int>)
  • flatten(std::initializer_list<std::initializer_list<int>>)

因此它允许从嵌套在 0 到 2 个初始化列表中的整数创建一个向量:)

这也说明了为什么我使用两个单独的模板参数:第一个 (Y) 是内部类型并且只是通过线程传递,第二个 (T) 有助于构建嵌套的初始化程序列出。可以通过从 T 中提取内部类型来摆脱 Y,但这最终比当前的解决方案更复杂:)

不幸的是,这个问题的官方答案似乎是否定的。 :(

来自https://en.cppreference.com/w/cpp/language/list_initialization:

A braced-init-list is not an expression and therefore has no type, e.g. decltype({1,2}) is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a braced-init-list, so given the declaration template<class T> void f(T); the expression f({1,2,3}) is ill-formed.

其他答案提供了一些不错的选择。 None 和我想要的一样干净。


编辑:

这是我能想到的最简洁的解决方案,它也适用于包含 int 以外类型的向量。我不得不将函数包装在结构中,因为你不能在 C++ 中部分特化函数模板。

这将自动为您计算出深度,因此您无需将其指定为模板参数,但它仍然不接受任意嵌套的初始化列表作为输入,因此您需要明确说明输入类型...

#include <type_traits>
#include <vector>

template <typename T>
struct dimensions : std::integral_constant<std::size_t, 0> {};

template <typename T>
struct dimensions<std::vector<T>> : std::integral_constant<std::size_t, 1 + dimensions<T>::value> {};

template <typename List, typename T, size_t N=dimensions<List>::value>
struct flatten
{
    std::vector<T> operator()(const List& l)
    {
        std::vector<T> v;

        for (const auto& e : l)
        {
            std::vector<T> tmp = flatten<decltype(e), T, N - 1>()(e);
            v.insert(v.end(), tmp.begin(), tmp.end());
        }

        return v;
    }
};

template <typename List, typename T>
struct flatten<List, T, 1>
{
    std::vector<T> operator()(const List& l)
    {
        return std::vector<T>(l.begin(), l.end());
    }
};

int main()
{
    std::vector<std::vector<std::vector<int>>> l = {
        { { 0, 1}, { 2, 3 } },
        { { 4, 5}, { 6, 7 } },
    };

    std::vector<int> f = flatten<decltype(l), int>()(l);
}