开发者问题收集

如何将 `std::array` 用于 `template<typename> class` 形式的模板参数?

2016-03-19
2053

请考虑以下 tree

template<typename T, template<typename> class Tuple>
class tree
{
private:
    T m_value;
    Tuple<tree> m_children;
};

template<typename T, std::size_t N>
using static_tree = tree<T, std::array<T, N>>;

该类定义不明确。 std::array<T, N> 不是 Tuple 的合适模板参数。我认为 static_tree 的意图很明确。我们可以做类似的事情

template<std::size_t N>
struct helper
{
    template<typename T>
    using type = std::array<T, N>;
};

template<typename T, std::size_t N>
using static_tree = tree<T, helper<N>::template type>;

没有 helper 类还有其他选择吗?

3个回答

我建议不要将辅助函数作为 std::array 的例外,而应将其作为规则。不要采用模板模板参数,而应采用 元函数类 参数。当任何地方的所有内容都是类型时(避免使用模板和非类型参数),模板元编程会变得容易得多:

template<typename T, typename TupleMfn>
class tree
{
private:
    using Tuple = TupleMfn::template apply<tree>;

    T m_value;
    Tuple m_children;
};

使用:

template <size_t N>
struct array_builder {
    template <class T>
    using apply = std::array<T, N>;
};

template <typename T, size_t N>
using static_tree = tree<T, array_builder<N>>;

这也将使其更容易与其他类型的容器一起使用,因为我们可以为模板模板制作一个包装器,该包装器将返回一个元函数:

template <template <typename...> class X>
struct as_metafunction {
    template <class... Args>
    using apply = X<Args...>;
}; 

template <typename T>
using vector_tree = tree<T, as_metafunction<std::vector>>;

如果您感觉特别活跃,可以提供一个适合元编程的 std::array 版本:

template <class T, class N>
struct meta_array : std::array<T, N::value> // obviously I am terrible at naming things
{ };

template <size_t N>
using size_t_ = std::integral_constant<size_t, N>;

然后为 tree 提供要应用的占位符参数:

template <class T, size_t N>
using static_tree = tree<T, meta_array<_, size_t_<N>>>;

template <class T>
using vector_tree = tree<T, std::vector<_>>;
Barry
2016-03-19

我认为您的问题包含一个根本问题,它与您明确提出的问题不同(它让我在之前的回答中有些困惑)。结合问题的不同部分,您本质上是在尝试实例化某个树类,该树类具有一个成员,该成员也是同一类的 std::array 。这显然是不可能的。您可能希望树应该包含一些 Tuple 指针 (智能或其他)。

一种方法是使用您的辅助类,但将类修改为

template<typename T, template<typename> class Tuple>
class tree
{
    // Indirection (I'm omitting the question of whether these should be
    //     smart pointers.
    Tuple<tree<T, Tuple> *> m_children;
};

另一种方法是将 Tuple 设为常规模板参数,如下所示:

#include <array>
#include <type_traits>

template<typename T, class Tuple>
class tree                                                                                                                                  
{
private:
    static_assert(
        std::is_same<void *, typename Tuple::value_type>::value, 
        "Tuple must be a container of void *");

private:
    T m_value;
    Tuple m_children;
};

template<typename T, std::size_t N>
using static_tree = tree<T, std::array<void *, N>>;

int main()
{
    static_tree<int, 8> t;
}

一方面,辅助类已被淘汰。另一方面, Tuplevoid * 的容器:实例化器知道这一点,并且类内部需要执行强制转换。这是一种权衡。我会坚持使用您的原始版本(当然,建议进行修改)。

Ami Tavory
2016-03-19

除非逻辑上如此,类 X 不能包含类 X 的实际实例的多个副本。

假设我们有

struct X {
  std::array<X, 2> data;
};

X 的唯一可能大小是无穷大,因为 sizeof(X) = 2*sizeof(X) ,并且 C++ 中的所有类型都有 sizeof(X)>=1

C++ 不支持无限大的类型。

您的第二个问题是类型实例不是模板。

template<typename T, template<typename> class Tuple>
class tree

这需要一个类型 T 和一个 模板 元组 。第二个参数 不是类型

template<typename T, std::size_t N>
using static_tree = tree<T, std::array<T, N>>;

此处,您的第二个参数 是类型 ,而不是模板。

template<std::size_t N>
struct array_of_size {
  template<class T>
  using result=std::array<T,N>;
};
template<typename T, std::size_t N>
using static_tree = tree<T, array_of_size<N>::template result>;

除了上述“无限大小问题”外,还能解决您的问题。此处我们将模板 array_of_size<N>::result 传递给 tree

要解决无限大小问题,您 必须 在数组中存储指针(或等效内容)。因此我们得到:

template<std::size_t N>
struct array_of_ups_of_size {
  template<class T>
  using result=std::array<std::unique_ptr<T>,N>;
};
template<typename T, std::size_t N>
using static_tree = tree<T, array_of_ups_of_size<N>::template result>;

现在您的 static_tree 有 N 个子节点,每个子节点都是指向类似 static_treeunique_ptr

由于析构函数问题,这仍然不起作用。

template<typename T, template<typename> class Tuple>
class tree
{
private:
  T m_value;
  Tuple<tree> m_children;
public:
  ~tree();
};

template<typename T, template<typename> class Tuple>
tree<T,Tuple>::~tree() = default;

我认为上述方法可以修复它,尽管这看起来很奇怪。

基本上,当您创建子节点数组时,树类型是不完整的。在析构时,将调用 delete。此时,树必须是完整的。通过推迟 dtor,我们希望能够解决这个问题。

我不确定模板是否需要这种技术,但它适用于非模板类。

Yakk - Adam Nevraumont
2016-03-20