Вывод аргумента шаблона с инициализацией списка параметров - PullRequest
0 голосов
/ 07 мая 2018

Я пытался создать класс, который представляет не принадлежащий многомерный вид массива (вроде N-мерного std::string_view), где размерность изменяется «динамически». Т.е. количество измерений и размерных размеров не привязано к классу, а указывается при доступе к элементам (через operator()). Следующий код суммирует функциональность, которую я ищу:

#include <array>
#include <cstddef>

template<typename T>
struct array_view {

    T* _data;

    // Use of std::array here is not specific, I intend to use my own, but similar in functionality, indices class.
    template<std::size_t N>
    T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
    {
        std::size_t offset = /* compute the simple offset */;

        return _data[offset];
    }

};

int main()
{
    int arr[3 * 4 * 5] = {0};

    array_view<int> view{arr};

    /* Access element 0. */
    // Should call array_view<int>::operator()<3>(std::array<std::size_t, 3>, std::array<std::size_t, 3>)
    view({5, 4, 3}, {0, 0, 0}) = 1;
}

Однако это не может скомпилировать (игнорируя очевидную синтаксическую ошибку в operator()) с

main.cpp: In function 'int main()':
main.cpp:28:27: error: no match for call to '(array_view<int>) (<brace-enclosed initializer list>, <brace-enclosed initializer list>)'
  view({5, 4, 3}, {0, 0, 0}) = 1;
                           ^
main.cpp:11:5: note: candidate: 'template<long unsigned int N> T& array_view<T>::operator()(std::array<long unsigned int, N>, std::array<long unsigned int, N>) const [with long unsigned int N = N; T = int]'
  T& operator()(std::array<std::size_t, N> dimensions, std::array<std::size_t, N> indices) const
     ^~~~~~~~
main.cpp:11:5: note:   template argument deduction/substitution failed:
main.cpp:28:27: note:   couldn't deduce template parameter 'N'
  view({5, 4, 3}, {0, 0, 0}) = 1;
                           ^

Я не эксперт по конкретизации / выводу шаблонов. Однако мне кажется, что компилятор пытается вывести N из std::initializer_list<int> аргументов, что не удается, поскольку объявлено, что operator() принимает std::array<std::size_t, N> аргументы. Следовательно, компиляция не удалась.

Выполнение другого, гораздо более упрощенного эксперимента , показывает аналогичные результаты:

template<typename T>
struct foo {
    T val;
};

struct bar {
    template<typename T>
    void operator()(foo<T>) {}
};

int main()
{
    bar b;
    b({1});
}

Выход:

main.cpp: In function 'int main()':
main.cpp:14:7: error: no match for call to '(bar) (<brace-enclosed initializer list>)'
  b({1});
       ^
main.cpp:8:10: note: candidate: 'template<class T> void bar::operator()(foo<T>)'
     void operator()(foo<T>) {}
          ^~~~~~~~
main.cpp:8:10: note:   template argument deduction/substitution failed:
main.cpp:14:7: note:   couldn't deduce template parameter 'T'
  b({1});

Кажется, что компилятор даже не пытается преобразовать {1} (который является действительной инициализацией foo<int>) в foo<int>, потому что он останавливается после неудачного вывода аргумента шаблона функции.

Так есть ли какой-нибудь способ добиться нужной мне функциональности? Есть ли какой-то новый синтаксис, который мне не хватает, или альтернативный подход, который делает то же самое, или это просто невозможно?

Ответы [ 2 ]

0 голосов
/ 07 мая 2018

Причина, по которой он не компилируется, состоит в том, что такие вещи, как {5, 4, 3} и {0, 0, 0} (называемые braced-init-list s), не похожи на выражения первого класса. У них нет типов. При выводе шаблона мы пытаемся сопоставить тип с выражением - и мы не можем сделать это для braced-init-list . И даже если бы мы могли, braced-init-list не является std::array любого рода, так что это не будет соответствовать. Для этого нам потребуется дополнительная языковая поддержка, которой у нас просто нет.

Есть два исключения.

Большой - std::initializer_list<T>. Но это имеет размер среды выполнения, который нам не нужен в этом случае, так как вы хотите, чтобы параметры dimensions и indices имели одинаковый размер (предположительно).

Другой - это необработанные массивы. Вы можете вывести T[N] из списка фигурных скобок. Таким образом, вы можете написать что-то вроде:

template <typename D, typename I, std::size_t N>
T& operator()(D const (&dimensions)[N], I const (&indices)[N]) const;

Это позволит вам написать view({5, 4, 3}, {0, 0, 0}), который выведет D и I как int и N как 3. Это также правильно предотвратит компиляцию view({5, 4, 3}, {0}) и view({5}, {0, 0}).

Вы можете добавить дополнительные ограничения, которые D и I являются целочисленными типами.

0 голосов
/ 07 мая 2018

Так есть ли способ добиться нужной мне функциональности? Есть какой-то новый синтаксис, который я пропускаю, или альтернативный подход, который делает то же самое, или это просто невозможно?

Очевидно, что вы можете указать значение N следующим образом

view.operator()<3U>({{5U, 4U, 3U}}, {{0U, 0U, 0U}}) = 1;

но я понимаю, что это уродливое решение.

Для альтернативного подхода ... если вы можете согласиться отказаться от второго массива (и, вызывая оператор, со вторым списком инициализатора) и если для вас нормально, использовать список шаблонов с переменным значением ... размер из вариационного списка становится измерением выжившего массива

template <typename ... Ts>
T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                Ts const & ... indices) const
 {
    std::size_t offset = 0 /* compute the simple offset */;

    return _data[offset];
 }

и вы можете использовать его следующим образом

view({{5U, 4U, 3U}}, 0U, 0U, 0U) = 1;

Очевидно, что использование indices внутри оператора может быть более сложным и может потребоваться добавить некоторую проверку типа Ts... (чтобы убедиться, что все они конвертируемы в std::size_t

Но я полагаю, вы также можете определить func() метод как ваш оригинальный operator()

template <std::size_t N>
T & func (std::array<std::size_t, N> const & dims,
          std::array<std::size_t, N> const & inds) const
 {
   std::size_t offset = 0 /* compute the simple offset */;

   return _data[offset];
 }

и вы можете позвонить с operator()

template <typename ... Ts>
T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                Ts const & ... indices) const
 { return func(dims, {{ indices... }}); }

Ниже приведен полный рабочий пример

#include <array>
#include <cstddef>

template <typename T>
struct array_view
 {
   T * _data;

   template <std::size_t N>
   T & func (std::array<std::size_t, N> const & dims,
             std::array<std::size_t, N> const & inds) const
    {
      std::size_t offset = 0 /* compute the simple offset */;

      return _data[offset];
    }


   template <typename ... Ts>
   T & operator() (std::array<std::size_t, sizeof...(Ts)> const & dims,
                   Ts const & ... indices) const
    { return func(dims, {{ indices... }}); }

 };

int main ()
 {
   int arr[3 * 4 * 5] = {0};

   array_view<int> view{arr};

   view({{5U, 4U, 3U}}, 0U, 0U, 0U) = 1;
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...