Операция valarray на месте дает другой результат как временное назначение

следующая программа:

#include<iostream>
#include<valarray>

using namespace std;

int main() {
int init[] = {1, 1};

// Example 1
valarray<int> a(init, 2);
// In-place assignment
a[slice(0, 2, 1)] = a[slice(0, 2, 1)] + valarray<int>(a[slice(0, 2, 1)]) * a[0];

for (int k = 0; k < 2; ++ k) {
cout << a[k] << ' ';  // Outputs 2 3
}
cout << endl;

// Example 2
valarray<int> b(init, 2);
// Temporary assignment
valarray<int> r = b[slice(0, 2, 1)] + valarray<int>(b[slice(0, 2, 1)]) * b[0];
b[slice(0, 2, 1)] = r;

for (int k = 0; k < 2; ++ k) {
cout << b[k] << ' '; // Outputs 2 2
}
cout << endl;
return 0;
}

выходы:

2 3
2 2

Правильный ответ 2 2 (<1 1> + <1 1> * 1 = <2 2>, Почему встроенная версия выводит что-то другое?

В случае, если это имеет значение, я собираю так:

g++ myprogram.cpp -o myprogram

И выход g++ -v является:

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.5' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.5)

2

Решение

Первый, a[slice(0, 2, 1)] имеет тип slice_array<T>и нет перегрузки operator+ принимая slice_array<T> объект или ссылка в качестве параметра.

Обратите внимание на возможные рабочие перегрузки operator+(const valarray<T>&, const valarray<T>&) это шаблон функции, хотя slice_array<T> может быть неявно преобразовано в valarray<T>аргумент шаблона T не может быть выведено из slice_array<T> аргумент.

Строго говоря, ваш код вызовет ошибку компиляции. По факту, Clang делает.


Во-вторых, вы должны знать, что есть некоторые методы оптимизации для операций valarray, Один хорошо известный метод шаблоны выражений, который вызывает ваши неожиданные результаты. Чтобы увидеть, как это работает, давайте рассмотрим более простой пример, который воспроизводит эту проблему:

valarray<int> a{1, 1};
a = a + a[0];
// now a is {2, 3} while {2, 2} is expected

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

В приведенном выше примере оптимизатор может выбрать оптимизацию результата a + a[0] быть прокси-объектом вместо valarray<int> временный характер. Прокси-объект просто хранит действие (не значение результата) добавления a[0] в a».

Когда прокси-объект затем назначается a, фактическая оценка происходит. Из сохраненного действия оптимизатор выберет a[i] + a[0] в a[i] для каждого i, Теперь разные порядки оценки в этом назначении будут приводить к разным результатам. Например, если компилятор назначает a[0] + a[0] в a[0], а затем назначает a[1] + a[0] (Вот a[0] изменяется на 2) на a[1]неожиданный результат {2, 3} производится.

Стандарт допускает существование такого прокси-объекта, но, похоже, неясно указано, как прокси-объект должен работать. Я лично думаю, что это ошибка компилятора, потому что просто оценивать a[0] и сохранение его значения до назначения решит эту проблему с минимальной потерей производительности.

2

Другие решения

Это похоже на отсутствующую перегрузку шаблона со старым компилятором. шаблон

valarray<T>& operator=( valarray<T>&& other ) noexcept;

существует в VS2017 и в GCC-7.1 но отсутствует в старшая версии. Выражение справа, кажется, оценивается и присваивается на каждой итерации. Простейший пример, демонстрирующий это:

#include <iostream>
#include <valarray>

int main()
{

std::valarray<int> a{2, 4, 8};
a = a + a[0];

for (auto n : a)
std::cout << n << " ";

std::cout << std::endl << std::endl;

return 0;
}

правильный вывод:

4 6 10

Однако старые компиляторы производить

4 8 12

Решение будет использовать обновленный компилятор или принудительное копирование

a = std::valarray<int>(a + a[0]);

Надеюсь это поможет

2

По вопросам рекламы [email protected]