Что не так с оператором std::valarray*?

Рассмотрим следующий MCVE, где у меня есть два массива значений, где w — это два раза v (попробуй здесь):

#include <valarray>

using namespace std;

int main() {
  valarray<int> v { 1, 2, 3 };

  for ([[maybe_unused]] auto x : v) {} // Ok

  auto w = v * 2;     // Leads to failure in loop below
  //valarray<int> w = v * 2; // Works
  //auto w = v*=2;      // Works
  //auto w = v; w *= 2; // Works

  for ([[maybe_unused]] auto x : w) {} // Failure here
}

Этот пример не компилируется с clang и gcc в последнем цикле (вывод gcc здесь):

error: no matching function for call to 'begin(std::_Expr<std::__detail::_BinClos<std::__multiplies, std::_ValArray, std::_Constant, int, int>, int>&)'

Источником проблемы, по-видимому, является decuced тип v * 2 (я предполагаю, что, поскольку явное написание типа работает, поэтому, похоже, происходит некоторое неявное преобразование).

Глядя на справочные примечания, кажется, что operator* может возвращать что-то отличное от std::valarray<T>. Я не понимаю причину этого, но более загадочным является то, что то же самое относится и к operator*=, за исключением того, что здесь работает мое задание auto. Я ожидаю, что возвращаемые значения operator*= и operator* будут здесь одинаковыми (дельта ссылки).

Итак, мои вопросы:

  • Это проблема/ошибка реализации? Или я что-то упускаю?
  • В чем причина справочные примечания (например, почему операторы могут возвращать что-то другое, что может не работать с std::begin/std::end)?

(Примечание: я пометил этот вопрос c++ 11, но, похоже, он применим ко всем версиям до 17)

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
0
577
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Существует трюк, называемый шаблонами выражений, который позволяет повысить эффективность составных выражений, но ужасно ломается при использовании auto.

Измените это:

auto w = v * 2;

к этому:

std::valarray<int> w = v * 2;

и ваш код работает.


Чтобы понять, почему мы хотим использовать шаблоны выражений, попробуйте следующее:

std::valarray<int> a = {1,2,3},b{4,5,6},c = {2,4,8};
std::valarray<int> r = (a+b*2)*c;

здесь шаблоны выражений избегают создания временного valarray a+b*2 или b*2, а вместо этого передают все выражение вниз и создают r с поэлементными операциями.

В (a+b*2)*c не создаются 3-элементные временные объекты valarray — только ряд объектов, описывающих структуру выражения и аргументы. При назначении фактическому valarray выражение оценивается поэлементно.

Но auto не превращается в valarray; он просто хранит объект шаблона выражения. Итак, ваш код ломается.

Я не знаю, какие версии стандарта разрешают это или нет; несмотря на это, некоторые реализации valarray используют это, и это повышает эффективность. Без него valarray откровенно отстой.

На самом деле это довольно интересная ситуация: угорь.is/С++ черновик/valarray.syn#4

chris 20.05.2019 16:23

Я бы добавил, что std::valarray был создан задолго до C++11, поэтому не было ни auto, ни циклов диапазона. Так что теперь эта оптимизация является проблемой. ИМО, это должно быть исправлено путем добавления begin и end к этим шаблонам выражений.

Marek R 20.05.2019 16:37

@marek Может быть, и нет, и это нелегко; begin и end, которые возвращают действительно полезные итераторы без ввода, требуют, до Rangesv3, резервного контейнера. Возможно, Rangesv3 сможет найти здесь хорошее решение.

Yakk - Adam Nevraumont 20.05.2019 16:39

Это хорошее объяснение! Я до сих пор удивляюсь, почему операторы а)* и *= ведут себя по-разному, посмотреть здесь (P.S. Я также пробовал libc++ в clang). Почему такое несоответствие? И б), почему шаблон выражения каким-то образом не преобразуется обратно в valarray «на месте»? Кажется, все-таки сделано в operator *=...

andreee 20.05.2019 17:08
*= сохраняет результат в левом соглашении, поэтому новый объект не создается. * создает новые объекты, поэтому ему требуется выделение и запись в память, поэтому создается прокси-объект, чтобы избежать ненужного выделения памяти и записи в этот участок памяти.
Marek R 20.05.2019 17:16

О Конечно. Это то, что уже предлагает ответ Адама. В случае *= мы все равно изменяем исходный объект, и нам нужно только вернуть *this, а в случае * мы можем избежать временного. Понял.

andreee 20.05.2019 17:19

@andreee Это может быть наивным объяснением, но если вы думаете об этих операциях как о выражениях, они разные. Один принимает три операнда, а другой только два, включая память. А в случае несамостоятельного оператора присваивания вы избежите создания временного хранилища, если введете «тип замены», о котором говорится в примечании к здесь. Унарная версия не имеет такого примечания, но вам гарантируется, что хранилище существует. «Тип замены» — это тип выражения, который может быть оценен как valarray (и др.)

luk32 20.05.2019 17:21

Я думаю, если бы у него были const начальный и конечный элементы (которые это не), то это не будет проблемой). Ой.

Lightness Races in Orbit 31.05.2019 14:37

@light для итерации ему пришлось бы выделить из-за того, как итераторы указаны в C++ 17 и более ранних версиях. Иван. вроде утилиту вижу, в то же время нет.

Yakk - Adam Nevraumont 31.05.2019 14:52

Почему он должен выделять? Я могу повторять std::array просто отлично

Lightness Races in Orbit 31.05.2019 15:10

@LightnessRacesinOrbit Потому что valarray имеет переменный размер, а промежуточные объекты просто несут ссылки на другие массивы и не имеют хранилища. Чтобы перебирать полученные значения, ему требуется резервное хранилище («настоящее» перебор генераторов является незаконным; генераторами могут быть только итераторы ввода, а begin в valarray не является входным итератором), и резервного хранилища не существует, поэтому как только begin вызывается резервное хранилище, оно должно существовать и сохраняться достаточно долго, пока не будет завершена итерация. Попробуйте написать. Это боль.

Yakk - Adam Nevraumont 31.05.2019 17:54

Другие вопросы по теме