如何将 `std::array` 用于 `template<typename> class` 形式的模板参数?
请考虑以下
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
类还有其他选择吗?
我建议不要将辅助函数作为
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<_>>;
我认为您的问题包含一个根本问题,它与您明确提出的问题不同(它让我在之前的回答中有些困惑)。结合问题的不同部分,您本质上是在尝试实例化某个树类,该树类具有一个成员,该成员也是同一类的
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;
}
一方面,辅助类已被淘汰。另一方面,
Tuple
是
void *
的容器:实例化器知道这一点,并且类内部需要执行强制转换。这是一种权衡。我会坚持使用您的原始版本(当然,建议进行修改)。
除非逻辑上如此,类 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_tree
的
unique_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,我们希望能够解决这个问题。
我不确定模板是否需要这种技术,但它适用于非模板类。