How can I use `std :: array` for the form template parameter of the` template <typename> class`?
Pay attention to 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 correct. std::array<T, N>
not a suitable template parameter for Tuple
. I assume the intent of static_tree
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 another option without helper
class?
Instead of the helper function being an exception to std::array
, I would suggest this rule. Instead of accepting a template template parameter, take the metafunction class parameter. Metaprogramming templates is much simpler when everything is a type everywhere (avoiding template templates and arguments other than type):
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 types of containers, since we can create a template template wrapper that returns a metaphor for us:
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 feel particularly sharp, you can provide a version of std::array
for metaprogramming:
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 specify placeholder arguments for 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<_>>;
I think your question contains a fundamental problem that is different from what you explicitly asked (this threw me off a bit in the previous iteration of this answer). By combining the various parts of your question, you are essentially trying to instantiate some tree-like class that has a member, which is std::array
the same class. This is obviously not possible. You probably want the tree to contain several Tuple
pointers (smart or others).
One way to do this would be to use your helper class, but changing 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; };
Another way will make Tuple
correct 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 void *
container: they know this, and the class itself must perform translations. This is a compromise. I would stick with your original version (with suggested changes, of course).
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, like sizeof(X)
= 2*sizeof(X)
, and all types in C ++ have sizeof(X)>=1
.
C ++ does not support infinitely large types.
The second problem is that type instances are not templates.
template<typename T, template<typename> class Tuple> class tree
this requires 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 pattern.
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>;
besides the aforementioned βproblems with infinite sizeβ, it will solve your problem. Here we pass the array_of_size<N>::result
template to the tree
.
To solve the problem with infinite size, you must store pointers (or something like that) in an 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 unique_ptr
for a similar static_tree
.
This still does not work due to problems with the destructor.
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 this fixes, oddly enough.
Basically, when you create an array of children, the tree type is incomplete. When destroyed, delete is called. At this point, the tree should be full. By putting dtor aside, we hopefully handle this problem.
I'm not sure if this method is needed for templates, but it is for non-template classes.
Or you can implement template parameter binding, as you suggested (a little 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>;