Почему оптимизация GCC не работает с valarrays?

Это простая С++-программа с использованием valarrays:

#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    const auto& res ( ratios_t::value_type(256) / a );
    for(const auto& r : ratios_t{res})
        std::cout << r << " " << std::endl;
    return 0;  
}

Если я компилирую и запускаю его следующим образом:

g++ -O0 main.cpp && ./a.out

Вывод будет таким, как ожидалось:

512 256 128 

Однако, если я компилирую и запускаю его следующим образом:

g++ -O3 main.cpp && ./a.out

Вывод:

0 0 0 

То же самое происходит, если я использую параметр оптимизации -O1.

Версия GCC (последняя в Archlinux):

$ g++ --version
g++ (GCC) 6.1.1 20160707

Однако, если я попробую с clang, оба

clang++ -std=gnu++14 -O0 main.cpp && ./a.out

и

clang++ -std=gnu++14 -O3 main.cpp && ./a.out

выдаст тот же правильный результат:

512 256 128 

Версия Clang:

$ clang++ --version
clang version 3.8.0 (tags/RELEASE_380/final)

Я также пробовал с GCC 4.9.2 на Debian, где исполняемый файл производит правильный результат.

Является ли это возможной ошибкой в ​​GCC или я делаю что-то неправильно? Может ли кто-нибудь воспроизвести это?

EDIT: мне удалось воспроизвести эту проблему также и на версию GCC 6 на Mac OS для Windows.

Ответ 1

valarray и auto не смешиваются хорошо.

Создает временный объект, затем применяет к нему operator/:

const auto& res ( ratios_t::value_type(256) / a );

В libstdС++ valarray используются шаблоны выражений, поэтому operator/ возвращает легкий объект, который ссылается на исходные аргументы и оценивает их лениво. Вы используете const auto&, который заставляет шаблон выражения привязываться к ссылке, но не продлевает время жизни временного объекта, на который ссылается шаблон выражения, поэтому, когда происходит оценка, временные выходят за пределы области видимости и ее память был повторно использован.

Это будет нормально работать, если вы выполните:

ratios_t res = ratios_t::value_type(256) / a;

Ответ 2

Это результат неосторожной реализации operator/ (const T& val, const std::valarray<T>& rhs) (и, скорее всего, других операторов над valarrays) с ленивой оценкой:

#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    float x = 256;
    const auto& res ( x / a );
    // x = 512;  //  <-- uncommenting this line affects the output
    for(const auto& r : ratios_t{res})
        std::cout << r << " ";
    return 0;
}

Когда строка "x = 512" закомментирована, выходной сигнал

512 256 128 

Раскомментируйте, что строка и выход изменяются на

1024 512 256 

Так как в вашем примере левый аргумент оператора деления является временным, результатом является undefined.

UPDATE

Как Джонатан Вакели правильно указал, реализация на основе lazy-оценки становится проблемой в этом примере из-за использования auto.