Рассмотрим следующий 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)





Существует трюк, называемый шаблонами выражений, который позволяет повысить эффективность составных выражений, но ужасно ломается при использовании 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 откровенно отстой.
Я бы добавил, что std::valarray был создан задолго до C++11, поэтому не было ни auto, ни циклов диапазона. Так что теперь эта оптимизация является проблемой. ИМО, это должно быть исправлено путем добавления begin и end к этим шаблонам выражений.
@marek Может быть, и нет, и это нелегко; begin и end, которые возвращают действительно полезные итераторы без ввода, требуют, до Rangesv3, резервного контейнера. Возможно, Rangesv3 сможет найти здесь хорошее решение.
Это хорошее объяснение! Я до сих пор удивляюсь, почему операторы а)* и *= ведут себя по-разному, посмотреть здесь (P.S. Я также пробовал libc++ в clang). Почему такое несоответствие? И б), почему шаблон выражения каким-то образом не преобразуется обратно в valarray «на месте»? Кажется, все-таки сделано в operator *=...
*= сохраняет результат в левом соглашении, поэтому новый объект не создается. * создает новые объекты, поэтому ему требуется выделение и запись в память, поэтому создается прокси-объект, чтобы избежать ненужного выделения памяти и записи в этот участок памяти.
О Конечно. Это то, что уже предлагает ответ Адама. В случае *= мы все равно изменяем исходный объект, и нам нужно только вернуть *this, а в случае * мы можем избежать временного. Понял.
@andreee Это может быть наивным объяснением, но если вы думаете об этих операциях как о выражениях, они разные. Один принимает три операнда, а другой только два, включая память. А в случае несамостоятельного оператора присваивания вы избежите создания временного хранилища, если введете «тип замены», о котором говорится в примечании к здесь. Унарная версия не имеет такого примечания, но вам гарантируется, что хранилище существует. «Тип замены» — это тип выражения, который может быть оценен как valarray (и др.)
Я думаю, если бы у него были const начальный и конечный элементы (которые это не), то это не будет проблемой). Ой.
@light для итерации ему пришлось бы выделить из-за того, как итераторы указаны в C++ 17 и более ранних версиях. Иван. вроде утилиту вижу, в то же время нет.
Почему он должен выделять? Я могу повторять std::array просто отлично
@LightnessRacesinOrbit Потому что valarray имеет переменный размер, а промежуточные объекты просто несут ссылки на другие массивы и не имеют хранилища. Чтобы перебирать полученные значения, ему требуется резервное хранилище («настоящее» перебор генераторов является незаконным; генераторами могут быть только итераторы ввода, а begin в valarray не является входным итератором), и резервного хранилища не существует, поэтому как только begin вызывается резервное хранилище, оно должно существовать и сохраняться достаточно долго, пока не будет завершена итерация. Попробуйте написать. Это боль.
На самом деле это довольно интересная ситуация: угорь.is/С++ черновик/valarray.syn#4