Передача вариативных параметров в уже шаблонно-вариационной функции

Название плохое, но лучше я придумать не мог. Не стесняйтесь менять это.

Вот шаблонный класс многомерного массива, над которым я сейчас работаю. Я пытаюсь максимально его оптимизировать:

#include <array>

template <typename T, std::size_t... Dimensions>
class multidimensional_array
{
    public:

        using value_type = T;
        using size_type = std::size_t;

    private:

        template<typename = void>
        static constexpr size_type multiply(void)
        {
            return 1u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type multiply(void)
        {
            return First * multidimensional_array::multiply<Other...>();
        }

    public:

        using container_type = std::array<value_type, multidimensional_array::multiply<Dimensions...>()>;
        using reference = value_type &;
        using const_reference = value_type const&;
        using iterator = typename container_type::iterator;

    private:

        container_type m_data_array;

        template<typename = void>
        static constexpr size_type linearise(void)
        {
            return 0u;
        }

        template<std::size_t First, std::size_t... Other>
        static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
        {
            return multidimensional_array::multiply<Other...>()*index + multidimensional_array::linearise<Other...>(indexes);
        }

    public:

        // Constructor
        explicit multidimensional_array(const_reference value = value_type {})
        {
            multidimensional_array::fill(value);
        }

        // Accessors
        reference operator()(std::size_t indexes...)
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        const_reference operator()(std::size_t indexes...) const
        {
            return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
        }

        // Iterators
        iterator begin()
        {
            return m_data_array.begin();
        }

        iterator end()
        {
            return m_data_array.end();
        }

        // Other
        void fill(const_reference value)
        {
            m_data_array.fill(value);
        }
};

Моя основная функция

int main(void)
{
    multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;
    int k = 0;

    for (auto& s : foo)
        s = k++;

    //std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
    return 0;
}

Компиляторы кода выше без предупреждения / ошибки. Но как только я раскомментирую часть std::cout, я получаю следующее:

g++-7 -std=c++17 -o foo.o -c foo.cpp -Wall -Wextra -pedantic
foo.cpp: In instantiation of ‘multidimensional_array<T, Dimensions>::value_type& multidimensional_array<T, Dimensions>::operator()(std::size_t, ...) [with T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}; multidimensional_array<T, Dimensions>::reference = int&; multidimensional_array<T, Dimensions>::value_type = int; std::size_t = long unsigned int]’:
foo.cpp:99:37:   required from here
foo.cpp:60:72: error: no matching function for call to ‘multidimensional_array<int, 2, 3, 4, 5, 6>::linearise<2, 3, 4, 5, 6>(std::size_t&)’
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:38:30: note: candidate: template<class> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise() [with <template-parameter-2-1> = <template-parameter-1-1>; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(void)
                              ^~~~~~~~~
foo.cpp:38:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: error: wrong number of template arguments (5, should be at least 0)
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
foo.cpp:44:30: note: candidate: template<long unsigned int First, long unsigned int ...Other> static constexpr multidimensional_array<T, Dimensions>::size_type multidimensional_array<T, Dimensions>::linearise(std::size_t, std::size_t, ...) [with long unsigned int First = First; long unsigned int ...Other = {Other ...}; T = int; long unsigned int ...Dimensions = {2, 3, 4, 5, 6}]
   static constexpr size_type linearise(std::size_t index, std::size_t indexes...)
                              ^~~~~~~~~
foo.cpp:44:30: note:   template argument deduction/substitution failed:
foo.cpp:60:72: note:   candidate expects 2 arguments, 1 provided
    return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes)];
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
Makefile:17: recipe for target 'foo.o' failed
make: *** [foo.o] Error 1

И теперь я знаю почему. У меня вопрос, как я могу исправить linearise, чтобы он мог пройти indexes, не проходя va_list и тому подобное? К сожалению, linearise уже является шаблоном, вариативной функцией, поэтому я не могу использовать махинации с вариативным шаблоном в этом отношении.


person Pippin    schedule 12.05.2018    source источник
comment
Неплохой вопрос, но это не минимальный воспроизводимый пример - это не минимальный.   -  person user202729    schedule 12.05.2018
comment
Хорошо, возможный дубликат.   -  person user202729    schedule 12.05.2018
comment
@ user202729 Похоже на разумный вопрос. Ваше поведение похоже на худшую проблему чрезмерной модерации SO.   -  person Arthur Tacca    schedule 12.05.2018
comment
@ArthurTacca Я сомневаюсь, что operator() и его константный вариант и утилита swap / fill были бы полезны в любом случае. Кроме того, я ненавижу прокрутку для чтения кода.   -  person user202729    schedule 12.05.2018
comment
Я согласен с тем, что вам не нужно вести себя так, как вы. В любом случае, я удалил функцию подкачки, но не функцию заполнения, поскольку она является частью конструктора. Мой код все еще очень длинный, не стесняйтесь оставлять эту тему навсегда.   -  person Pippin    schedule 12.05.2018
comment
@Pippin Вам не нужны ни функция fill, ни конструктор. Если оставить нулевую инициализацию, это не повлияет на функциональность.   -  person user202729    schedule 12.05.2018
comment
@Pippin Я исправил свой ответ на случай, если вы видели только старую версию.   -  person Arthur Tacca    schedule 12.05.2018


Ответы (3)


Как и в предыдущем вопросе, проблема в том, что следующие подписи

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes...)

reference operator()(std::size_t indexes...)

const_reference operator()(std::size_t indexes...) const

не то, что вы имеете в виду (indexes список с переменным числом std::size_t), но в точности эквивалентны

template<std::size_t First, std::size_t... Other>
static constexpr size_type linearise(std::size_t index,
                                     std::size_t indexes,
                                     ...)

reference operator()(std::size_t indexes, ...)

const_reference operator()(std::size_t indexes, ...) const

где indexes - это одиночный std::size_t, за которым следует необязательная последовательность аргументов в стиле C.

Простое решение (вы отметили C ++ 17, но доступно начиная с C ++ 11) основано на использовании вариативных шаблонов.

Например, следующим образом

template <std::size_t First, std::size_t ... Other, typename ... Ts>
static constexpr size_type linearise (std::size_t index,
                                      Ts ... indexes)
 { return multidimensional_array::multiply<Other...>() * index
        + multidimensional_array::linearise<Other...>(indexes...); }

  // Accessors
  template <typename ... Ts>
  reference operator() (Ts ... indexes)
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

  template <typename ... Ts>
  const_reference operator() (Ts ... indexes) const
   { return m_data_array[
        multidimensional_array::linearise<Dimensions...>(indexes...)]; }

Ниже приведен ваш код, измененный и компилируемый.

#include <array>
#include <iostream>

template <typename T, std::size_t ... Dimensions>
class multidimensional_array
 {
   public:
      using value_type = T;
      using size_type  = std::size_t;

   private:
      template <typename = void>
      static constexpr size_type multiply ()
       { return 1u; }

      template <std::size_t First, std::size_t ... Other>
      static constexpr size_type multiply(void)
       { return First * multidimensional_array::multiply<Other...>(); }

   public:
      using container_type  = std::array<value_type,
               multidimensional_array::multiply<Dimensions...>()>;
      using reference       = value_type &;
      using const_reference = value_type const &;
      using iterator        = typename container_type::iterator;

   private:
      container_type m_data_array;

      template <typename = void>
      static constexpr size_type linearise ()
       { return 0u; }

      template <std::size_t First, std::size_t ... Other, typename ... Ts>
      static constexpr size_type linearise (std::size_t index,
                                            Ts ... indexes)
       { return multidimensional_array::multiply<Other...>() * index
              + multidimensional_array::linearise<Other...>(indexes...); }

   public:
      // Constructor
      explicit multidimensional_array (const_reference value = value_type{})
       { multidimensional_array::fill(value); }

      // Accessors
      template <typename ... Ts>
      reference operator() (Ts ... indexes)
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      template <typename ... Ts>
      const_reference operator() (Ts ... indexes) const
       { return m_data_array[
            multidimensional_array::linearise<Dimensions...>(indexes...)]; }

      // Iterators
      iterator begin ()
       { return m_data_array.begin(); }

      iterator end ()
       { return m_data_array.end(); }

      // Other
      void fill (const_reference value)
       { m_data_array.fill(value); }
 };

int main ()
 {
   multidimensional_array<int, 2u, 3u, 4u, 5u, 6u> foo;

   int k{ 0 };

   for ( auto & s : foo )
      s = k++;

   std::cout << foo(0u, 0u, 0u, 1u, 0u) << std::endl;
 }

Бонусное предложение.

Вы отметили C ++ 17, чтобы можно было использовать «сворачивание».

Таким образом, вы можете заменить пару multiply() шаблонных функций

  template <typename = void>
  static constexpr size_type multiply ()
   { return 1u; }

  template <std::size_t First, std::size_t ... Other>
  static constexpr size_type multiply ()
   { return First * multidimensional_array::multiply<Other...>(); }

с одним сложенным

  template <std::size_t ... Sizes>
  static constexpr size_type multiply ()
   { return ( 1U * ... * Sizes ); } 
person max66    schedule 12.05.2018
comment
Мой код теперь отлично компилируется и работает, спасибо. Я не знал, что возможно иметь более одного вариативного шаблона. Какая важная вещь, о которой нужно помнить. - person Pippin; 12.05.2018
comment
@Pippin - Да: в некоторых случаях у вас может быть более одного списка вариативных шаблонов; но ... сложно. В любом случае ... добавил бонусное предложение. - person max66; 12.05.2018

Мой подход аналогичен подходу в этом ответе, за исключением того, что вместо использования std::tuple для хранения списка типов < / strong>, я определяю свой собственный тип size_t_pack для хранения (во время компиляции) списка size_t.

using std::size_t;

template<size_t... values>
struct size_t_pack{};

template<size_t first_value,size_t... rest_values>
struct size_t_pack<first_value,rest_values...>{
    static constexpr size_t first=first_value;
    using rest=size_t_pack<rest_values...>;
    static constexpr size_t product=first*rest::product;
};

template<>struct size_t_pack<>{
    static constexpr size_t product=1;
};

Определяет элементы: first, rest (в случае, если он не пустой) и product (поскольку невозможно специализировать функцию с использованием шаблонов аргумента шаблона, насколько мне известно, другой выбор - if constexpr и сделать проверку поддержки типа для empty )

С этим легко определить функцию linearize:

template<class dimensions,class... SizeTs>
static constexpr size_type linearise(std::size_t index, SizeTs... indices)
{
    using restDimensions=typename dimensions::rest;
    return restDimensions::product *index + 
    multidimensional_array::linearise<restDimensions>(indices...);
}

Использование std::tuple для хранения списка типов (SizeTs) также возможно, хотя, насколько мне известно, частичная специализация структуры все еще требуется.

person user202729    schedule 12.05.2018

Вам нужно сделать индексы пакетом параметров, сделав функцию operator() шаблоном, и расширить пакет параметров, когда вы его используете, поместив после этого ...:

    template <class... DimensionType>
    const_reference operator()(DimensionType... indexes) const
    {
        return m_data_array[multidimensional_array::linearise<Dimensions...>(indexes...)];
    }

См. расширение пакета параметров

Код по-прежнему не будет компилироваться из-за аналогичной проблемы в linearize(), но это приведет вас на верный путь.

person Arthur Tacca    schedule 12.05.2018