I encountered unexpected behavior when mixing the output of boost::join
with std::views::transform
(shown below). The compiler issues no warnings whatsoever. Fortunately, an address sanitizer detects an invalid memory access in get2b()
.
The get1b()
function follows the same pattern as get2b()
, but that code works correctly. Given the possibility of UB, how can I be sure that a constructed range is legal? My paranoid side wants to write get1b()
as return std::move(rng) | ...
.
https://www.godbolt.org/z/Y77YW3jYb
#include <array>
#include <ranges>
#include <algorithm>
#include <iostream>
#include <iterator>
#include "boost/range/join.hpp"
#include "boost/range/adaptor/transformed.hpp"
inline auto square = [](int x) { return x*x; };
struct A {
std::array<std::vector<int>, 3> m_data{ std::vector{1, 2}, std::vector{3, 4, 5}, std::vector{6} };
auto join1() const { return m_data | std::views::join; }
auto join2() const { return boost::join(m_data[0], boost::join(m_data[1], m_data[2])); }
auto get1a() const { return join1() | std::views::transform(square); }
auto get1b() const { auto rng = join1(); return rng | std::views::transform(square); }
#if __GNUC__ >= 12
auto get2a() const { return join2() | std::views::transform(square); }
#endif
auto get2b() const { auto rng = join2(); return rng | std::views::transform(square); }
auto get2c() const { auto rng = join2(); return rng | boost::adaptors::transformed(square); }
};
template<std::ranges::range R>
void print(R&& r)
{
std::ranges::copy(r, std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
}
int main()
{
print(A{}.get1a());
print(A{}.get1b());
#if __GNUC__ >= 12
print(A{}.get2a());
#endif
// print(A{}.get2b()); <-- undefined behavior
print(A{}.get2c());
return 0;
}
P.S. The only reason why I even bothered to do this is because some of my team members were affected by Intellisense to not work properly on the IDE, which is ultimately caused by https://github.com/llvm/llvm-project/issues/44178. (sigh)