Как я могу использовать `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?


person 0xbadf00d    schedule 19.03.2016    source источник
comment
Если у вас есть tree, почему бы не сделать Tuple параметром типа, а не параметром шаблона?   -  person Alan Stokes    schedule 20.03.2016
comment
@AlanStokes Как бы я определил static_tree в этом случае? Это была бы бесконечная рекурсия, не так ли?   -  person 0xbadf00d    schedule 20.03.2016
comment
Дело в том, что std::array нужен целочисленный параметр. Невозможно указать его где-то. Что касается того, где это находится, есть много альтернатив, и ваша не так уж и очевидна.   -  person davidhigh    schedule 20.03.2016
comment
@davidhigh Я надеялся, что для параметров шаблона будет какое-то bind.   -  person 0xbadf00d    schedule 20.03.2016
comment
@ 0xbadf00d: существует довольно много реализаций этого, но они не могут быть обобщены на нетиповые параметры шаблона, поскольку они не смешиваются с пакетами параметров.   -  person ildjarn    schedule 20.03.2016
comment
Ваш помощник уже является чем-то вроде bind, но явным. Более общий вариант невозможен, как упоминалось @idljarn: причина этого в том, что вы не можете обычно указывать параметры шаблона результирующего шаблона класса... вы не можете использовать для этого пакет параметров, так как в каждой позиции вы также может иметь параметр шаблона, отличный от типа.   -  person davidhigh    schedule 20.03.2016


Ответы (4)


Вместо того, чтобы вспомогательная функция была исключением для 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<_>>;
person Barry    schedule 19.03.2016
comment
Ваш tree не является деревом, так как m_children является кортежем T. - person 0xbadf00d; 22.03.2016

Я думаю, что ваш вопрос содержит фундаментальную проблему, которая отличается от того, что вы явно спрашивали (это немного сбило меня с толку в предыдущей итерации этого ответа). Объединяя различные части вашего вопроса, вы, по сути, пытаетесь создать экземпляр некоторого класса дерева, в котором есть член, который также является 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;
}

С одной стороны, вспомогательный класс был исключен. OTOH, Tuple является контейнером void *: создатели экземпляров знают об этом, и класс внутри должен выполнять приведения типов. Это компромисс. Я бы придерживался вашей первоначальной версии (с предложенными изменениями, конечно).

person Ami Tavory    schedule 19.03.2016
comment
Вы знаете, что согласно вашему определению static_tree не является деревом, верно? Дети дерева тоже должны быть деревьями. - person 0xbadf00d; 20.03.2016
comment
@ 0xbadf00d Вы правы, но, поразмыслив, я думаю, что это было вызвано какой-то проблемой в вашем вопросе. Смотрите обновление. - person Ami Tavory; 20.03.2016

Класс 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 Tuple. Второй аргумент не является типом.

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 дочерних элементов, каждый из которых является unique_ptr похожим static_tree.

Это все еще не работает из-за проблем с деструктором.

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;

Я думаю, вышеизложенное исправляет это, как это ни странно.

По сути, когда вы создаете массив дочерних элементов, тип дерева неполный. При уничтожении вызывается удаление. В этот момент дерево должно быть завершено. Откладывая dtor, мы надеемся решить проблему.

Я не уверен, требуется ли этот метод для шаблонов, но он подходит для классов, не являющихся шаблонами.

person Yakk - Adam Nevraumont    schedule 20.03.2016

Или вы можете реализовать привязку параметра шаблона, как вы предложили (немного более общий, чем ваш 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>;
person Charles Pehlivanian    schedule 20.03.2016