How can I use `std::array` for a template parameter of the form `template<typename> class`?

↘锁芯ラ 提交于 2019-12-07 06:13:50

问题


Please consider the following tree class

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>>;

which is not well-defined. std::array<T, N> is not a suitable template parameter for Tuple. I assume the intend of static_tree is clear. We could do something like

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>;

Is there any other option without the helper class?


回答1:


Rather than having the helper function be an exception for std::array, I'd propose that be the rule. Instead of taking a template template parameter, take a metafunction class parameter. Template metaprogramming is a lot easier when everything everywhere is a type (avoiding template templates and non-type arguments):

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

    T m_value;
    Tuple m_children;
};

with:

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>>;

This will make it easier to use with other kinds of containers as well, since we can make a wrapper for template templates that gives us back a metafunction:

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>>;

If you're feeling especially feisty, you can provide a metaprogramming-friendly version of 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>;

And then provide placeholder args for tree to apply:

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<_>>;



回答2:


I think your question contains a fundamental problem which is different from what you were explicitly asking (it threw me off a bit in the previous iteration of this answer). Combining the different parts of your question, you're essentially trying to instantiate some tree class that has a member that is an std::array of the same class as well. This is obviously impossible. You probably want that the tree should hold some Tuple of pointers (smart or otherwise).

One way to do so would be to use your helper class, but modifying the class to

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;
};

A different way would make Tuple a regular template parameter, as follows:

#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;
}

On the one hand, the helper class has been eliminated. OTOH, Tuple is a container of void *: the instantiators are aware of this, and the class internally needs to perform casts. It's a tradeoff. I would stick to your original version (with the modifications suggested, of course).




回答3:


A class X cannot contain multiple copies of actual instances of class X, except logically.

An if we have

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

the only possible size for X is infinity, as sizeof(X) = 2*sizeof(X), and all types in C++ have sizeof(X)>=1.

C++ does not support infinitely large types.

Your second problem is that type instances are not templates.

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

this takes a type T and a template Tuple. The second argument is not a type.

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

here, your second argument is a type, not a template.

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>;

would, other than the above "infinite size problem", solve your problem. Here we are passing the template array_of_size<N>::result to tree.

To solve the infinite size problem, you must store pointers (or something equivalent) in the array. So we get:

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>;

and now your static_tree has N children, each of which is a unique_ptr to a similar static_tree.

This still doesn't work, because of destructor issues.

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;

I think the above fixes it, strange as it may seem.

Basically, when you make the array of children, the tree type is incomplete. At destruction, delete is called. At that point, tree must be complete. By deferring the dtor, we hopefully deal with the problem.

I am uncertain if this technique is required for templates, but it is for non-template classes.




回答4:


Or you could implement a template parameter bind as you suggested (slightly more general than your helper):

template<std::size_t N, template<typename,std::size_t> class T>
struct bind2nd
{
    template<typename F>
    using type = T<F,N>;
};

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


来源:https://stackoverflow.com/questions/36108029/how-can-i-use-stdarray-for-a-template-parameter-of-the-form-templatetypena

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!