Функция element_wise_add для Boost.MultiArray в C ++
Это дополнительный вопрос для функции суммирования для Boost.MultiArray в C ++ . Помимо операции суммирования всех элементов, я пытаюсь сосредоточиться здесь на поэлементной операции. Основная идея этого вопроса - реализовать element_wise_addфункцию для Boost.MultiArray. Цель этой element_wise_addфункции - выполнить поэлементное сложение двух boost::multi_arrays. Функция element_wise_addимеет два входных параметра input1и input2для операции поэлементного добавления, а возвращаемый тип - это поэлементный результат.
template<class T> requires is_summable<T>
auto element_wise_add(const T& input1, const T& input2)
{
return input1 + input2;
}
// Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::detail::multi_array::const_sub_array<T, Dims>& input1, const boost::detail::multi_array::const_sub_array<T, Dims>& input2)
{
boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
for (typename boost::detail::multi_array::const_sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
{
output[i] = element_wise_add(input1[i], input2[i]);
}
return output;
}
// Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::detail::multi_array::sub_array<T, Dims>& input1, const boost::detail::multi_array::sub_array<T, Dims>& input2)
{
boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
for (typename boost::detail::multi_array::sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
{
output[i] = element_wise_add(input1[i], input2[i]);
}
return output;
}
// Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::multi_array<T, Dims>& input1, const boost::multi_array<T, Dims>& input2)
{
if (*input1.shape() != *input2.shape()) // if shape is different
{
return input1; // unable to perform element-wise add operation
}
boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
for (typename boost::multi_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
{
output[i] = element_wise_add(input1[i], input2[i]);
}
return output;
}
Используемая is_summableконцепция:
template<typename T>
concept is_summable = requires(T x) { x + x; };
Проверка этой element_wise_addфункции выглядит следующим образом.
// Create a 3D array that is 3 x 4 x 2
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents[3][4][2]);
// Assign values to the elements
int values = 0;
for (index i = 0; i != 3; ++i)
for (index j = 0; j != 4; ++j)
for (index k = 0; k != 2; ++k)
A[i][j][k] = values++;
for (index i = 0; i != 3; ++i)
for (index j = 0; j != 4; ++j)
for (index k = 0; k != 2; ++k)
std::cout << A[i][j][k] << std::endl;
auto DoubleA = element_wise_add(A, A);
for (index i = 0; i != 3; ++i)
for (index j = 0; j != 4; ++j)
for (index k = 0; k != 2; ++k)
std::cout << DoubleA[i][j][k] << std::endl;
Все предложения приветствуются.
На какой вопрос это продолжение?
Функция суммирования для Boost.MultiArray в C ++
Какие изменения были внесены в код с момента последнего вопроса?
Предыдущий вопрос - это реализация функции суммирования для Boost.MultiArray, и основная идея этого вопроса - реализовать
element_wise_addфункцию для Boost.MultiArray.Почему запрашивается новый обзор?
Подобное использование трех типов перегрузить функцию
boost::multi_array,boost::detail::multi_array::sub_arrayиboost::detail::multi_array::const_sub_arrayпоявляется снова. Я знаю, может, это плохая идея. Однако я не могу придумать лучшего способа. Более того, обработка исключений для ситуации «форма отличается» не идеальна. В случае сboost::detail::multi_array::sub_arrayиboost::detail::multi_array::const_sub_arrayя не уверен, что нужно вернуть обратно. Я когда-либо пробовал что-тоstd::optionalподобное:template<class T, std::size_t Dims> requires is_summable<T> auto element_wise_add(const boost::detail::multi_array::const_sub_array<T, Dims>& input1, const boost::detail::multi_array::const_sub_array<T, Dims>& input2) { std::optional<boost::detail::multi_array::const_sub_array<T, Dims>> final_output; if (*input1.shape() != *input2.shape()) // if shape is different { final_output = std::nullopt; } else { boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape())); for (typename boost::detail::multi_array::const_sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++) { output[i] = element_wise_add(input1[i], input2[i]); } final_output = output; } return final_output; }И это:
template<class T, std::size_t Dims> requires is_summable<T> auto element_wise_add(const boost::detail::multi_array::sub_array<T, Dims>& input1, const boost::detail::multi_array::sub_array<T, Dims>& input2) { std::optional<boost::detail::multi_array::sub_array<T, Dims>> final_output; if (*input1.shape() != *input2.shape()) // if shape is different { final_output = std::nullopt; } else { boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape())); for (typename boost::detail::multi_array::sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++) { output[i] = element_wise_add(input1[i], input2[i]); } final_output = output; } return final_output; }Однако это настолько сложно в использовании ( для доступа к содержимому в структуре требуется использование функции
.value()or ) и, на мой взгляд, немного ужасно. Если есть предложения или возможные улучшения, сообщите мне!.value_or()std::optional<>
Ответы
Подумайте о том, чтобы превратить это в operator+()
Поскольку поэлементное добавление является довольно распространенной и естественной операцией (например, STL поддерживает это для std::valarray), может быть более интуитивно понятнее перегрузка operator+()вместо создания функции element_wise_add(). См. Этот вопрос для возможной реализации, которая также легко распространяется на других операторов.
Еще одним преимуществом создания this operator+()является то, что он boost::multi_arrayудовлетворяет саму себя is_summable, поэтому без добавления явной поддержки рекурсивных контейнеров будет работать следующее:
boost::multi_array<boost::multi_array<double, 2>, 3> array1, array2;
auto array3 = array1 + array2;
Обработка несовпадающих размеров массива
Я бы действительно не std::optionalстал сигнализировать об ошибках математических операций. Я вижу два пути:
Убедитесь, что размеры возвращаемого массива являются максимальными размерами двух входных массивов. Так что, если добавить
{{1}, {2}}в{{3, 5}}результат будет{{4, 5}, {2, 0}}.Бросьте std::logic_error, в предположении, что это ошибка программирования, чтобы добавить две несовпадающие ошибки.
Избегайте повторения
Вы в основном пишете одно и то же трижды, единственное отличие состоит в том, являются ли входные данные обычными boost::multi_arrays, sub_arrays или const_sub_arrays. Чтобы избежать этого, вы хотите сделать входные типы шаблонными, а чтобы гарантировать, что он соответствует только boost::multi_arrayсвязанным типам, просто напишите концепцию для этого. Опять же, вы можете использовать выражения, которые вы уже используете для этого:
template<T>
concept is_multi_array = requires(T x) {
x.shape();
boost::multi_array(x);
};
Это проверит, что тип xимеет функцию- shape()член и boost::multi_arrayможет быть скопирован из него. Тогда просто напишите:
template<class T> requires is_multi_array<T>
auto element_wise_add(const T& input1, const T& input2)
{
if (*input1.shape() != *input2.shape())
{
throw std::logic_error("array shape mismatch");
}
boost::multi_array output(input1);
for (decltype(+input1.shape()[0]) i = 0; i < input1.shape()[0]; i++)
{
output[i] = element_wise_add(input1[i], input2[i]);
}
return output;
}
Недостатком здесь является то, что весь input1массив копируется в него outputбез уважительной причины, за исключением того, что я не вижу, как построить новый multi_arrayболее эффективно, не углубляясь в ад типов, который приходит с ним boost::multi_array. Возможно, это можно передать другой функции шаблона.
Вы также заметите, что я изменил способ определения типа i: я использую decltype(), но поскольку это сохраняет const-ness, я должен отбросить его. Есть разные способы сделать это, здесь я использовал унарный +прием.
Обработка большего количества комбинаций
Что делать, если я хочу добавить sub_arrayк обычному стилю multi_arrayтакого же размера? При вашем подходе вам придется обрабатывать все возможные комбинации по отдельности, но в показанном мной примере одной функции вы можете просто написать:
template<class T1, class T2> requires (is_multi_array<T1> && is_multi_array<T2>)
auto element_wise_add(const T1& input1, const T2& input2)
{
...
}
Однако также учтите, что вы можете добавить массив двойных чисел к массиву целых чисел. Это можно заставить работать, если вы также напишете:
template<typename T1, typename T2>
concept is_summable = requires(T1 x, T2 y) { x + y; };
template<class T1, class T2> requires is_summable<T1, T2>
auto element_wise_add(const T1& input1, const T2& input2)
{
return input1 + input2;
}
Хотя для того, чтобы сделать его по-настоящему полезным, вы хотите, чтобы тип outputимел тип значения, который соответствует результату добавления типов значений двух входных массивов.