11

This is a simple c++ program using 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;  
}

If I compile and run it like this:

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

The output is as expected:

512 256 128 

However, if I compile and run it like this:

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

The output is:

0 0 0 

Same happens if I use -O1 optimization parameter.

GCC version is (latest in Archlinux):

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

However, if I try with clang, both

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

and

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

produce the same correct result:

512 256 128 

Clang version is:

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

I've also tried with GCC 4.9.2 on Debian, where executable produces the correct result.

Is this a possible bug in GCC or am I doing something wrong? Can anyone reproduce this?

EDIT: I managed to reproduce the issue also on Homebrew version of GCC 6 on Mac OS.

DoDo
  • 2,248
  • 2
  • 22
  • 34
  • Using http://melpon.org/wandbox it appears the behavior changes from 4.9.3 to 5.1. – NathanOliver Jul 13 '16 at 14:44
  • Unfortunately, in my codebase I've also managed to reproduce similar issue (but with uint32_t) even on GCC 4.9.3, however it works when put in minimal example. I am investigating... – DoDo Jul 13 '16 at 16:01

2 Answers2

6

valarray and auto do not mix well.

This creates a temporary object, then applies operator/ to it:

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

The libstdc++ valarray uses expression templates so that operator/ returns a lightweight object that refers to the original arguments and evaluates them lazily. You use const auto& which causes the expression template to be bound to the reference, but doesn't extend the lifetime of the temporary that the expression template refers to, so when the evaluation happens the temporary has gone out of scope, and its memory has been reused.

It will work fine if you do:

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

Update: as of today, GCC trunk will give the expected result for this example. I've modified our valarray expression templates to be a bit less error-prone, so that it's harder (but still not impossible) to create dangling references. The new implementation should be included in GCC 9 next year.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Thank you for detailed answer. I will accept it. I would just like to note that even if I write `auto res (ratios_t::value_type(256)/a)`, I still get the same result with optimisations turned on. However, your suggestion works (i.e. not using `auto`). With both clang and msvc original code worked correctly. – DoDo Jul 13 '16 at 19:39
  • Yes, because `auto` deduces the type of the expression template that contains a reference to the expired temporary. You need to explicitly create a `valarray`, to force the evaluation to happen before the temporary goes away. As I said, `auto` and `valarray` don't mix well. – Jonathan Wakely Jul 13 '16 at 19:56
3

It's the result of careless implementation of operator/ (const T& val, const std::valarray<T>& rhs) (and most probably other operators over valarrays) using lazy evaluation:

#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;
}

With the "x = 512" line commented out, the output is

512 256 128 

Uncomment that line and the output changes to

1024 512 256 

Since in your example the left-hand side argument of the division operator is a temporary, the result is undefined.

UPDATE

As Jonathan Wakely correctly pointed out, the lazy-evaluation based implementation becomes a problem in this example due to the usage of auto.

Community
  • 1
  • 1
Leon
  • 31,443
  • 4
  • 72
  • 97
  • Thank you for your answer. Jonathan was first so I accepted his answer, but your is also very informative. Too bad SO doesn't let you accept multiple answers :-). – DoDo Jul 13 '16 at 19:50