1

I have some weird behaviours when using boost::adaptors::filter. Let's note that I created a small example that respects my larger project contraints. With the following short example I have a segfault if compiled with GCC optimisations, and works like a charm with no optimisation. Of course, my goal is to have it working with -O3.

#include <cstdlib>
#include <cstdio>

#include <memory>
#include <boost/range/adaptors.hpp>
#include <boost/range/any_range.hpp>
#include <vector>
#include <iostream>

class TestClass {
    std::string _id="";
public:
    explicit TestClass(std::string i) : _id(i) {}
    std::string id() {return _id;}
};
typedef std::unique_ptr<TestClass> TestClassPtr;
typedef boost::any_range<TestClass*, boost::forward_traversal_tag> IterTestClass;
typedef std::vector<TestClassPtr> VecTestClass;

IterTestClass get_iter(const VecTestClass &in) {
    return boost::adaptors::transform(
        boost::adaptors::filter(in,
            [](const TestClassPtr &e) { return e->id() == "a"; }
        ),
        [](const TestClassPtr &e) ->TestClass* {return e.get();}
    );
}

int main(int argc, char **argv) {
    VecTestClass val;
    for(size_t i=0 ; i < argc ; ++i)
        val.push_back(TestClassPtr(new TestClass(argv[i])));

    for(TestClass *v : get_iter(val))
        std::cout << v->id() << std::endl;
    
    return EXIT_SUCCESS;
}

Good run (opti -O0):

g++ -o boostbug main.cpp -O0 -g -std=gnu++17
./boostbug b ab a a b c
a
a

Segfault run (opti -01 or -03):

g++ -o boostbug main.cpp -O1 -g -std=gnu++17
./boostbug b ab a a b c
#0  std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string (__str=<error reading variable: Cannot access memory at address 0xf>, this=0x7fffffffd910)
    at /usr/include/c++/11.2.0/bits/basic_string.h:459
#1  TestClass::id[abi:cxx11]() (this=0x7) at src/boostbug/main.cpp:14
#2  main (argc=0x7, argv=<optimized out>) at src/boostbug/main.cpp:35
#3  0x00007ffff7a8a310 in __libc_start_call_main () from /usr/lib/libc.so.6
#4  0x00007ffff7a8a3c1 in __libc_start_main_impl () from /usr/lib/libc.so.6
#5  0x00005555555561a5 in _start ()

Extremely weird run (opti -O2):

g++ -o boostbug main.cpp -O2 -g -std=gnu++17
./boostbug b ab a a b c
c
c

Any idea?

Config:

  • ArchLinux
  • gcc (GCC) 11.2.0
  • boost 1.78.0-2

Solution is to change the declaration of the type IterTestClass to add a 3rd argument to the any_range declaration, such as:

typedef boost::any_range<TestClass*, boost::forward_traversal_tag, TestClass*> IterTestClass;
sehe
  • 374,641
  • 47
  • 450
  • 633
Psylox
  • 31
  • 3
  • Often such behaviour is a sign of undefined behaviour. Try to simplify your code. The mixture of const references, smart pointers and raw pointers is alarming and makes it very hard to understand what is going on. – Jakob Stark May 31 '22 at 09:07
  • 1
    Duplicate of [this question](https://stackoverflow.com/q/44246754/1983398)? – ssbssa May 31 '22 at 12:05
  • @ssbssa it's not the same problem with a missing "const". But it actually helped me solve it. If I change the declaration: ```typedef boost::any_range IterTestClass;``` to add the 3rd parameter it works! Brilliant. I however do not really understand why. – Psylox May 31 '22 at 12:58

1 Answers1

0

The "reference" type from the iterator adaptor is hard-coded - by you - to be a reference to TestClass*. However, that pointer value is a temporary.

Hence you will get UB.

I would make the return type deduced, which also prevents the type-erasure runtime overhead:

auto get_iter(const VecTestClass &in) { // ...

Also, consider the adaptor syntax: http://coliru.stacked-crooked.com/a/98a5ca8f1568460e

#include <boost/range/adaptors.hpp>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct X {
  std::string _id;
};

using Ptr = std::unique_ptr<X>;
using Ptrs = std::vector<Ptr>;

auto get_iter(Ptrs const &in) {
  using namespace boost::adaptors;
  return in | indirected | filtered([](X const &e) { return e._id == "a"; });
}

auto main(int argc, char **argv) -> int {
  Ptrs xs;
  std::transform( //
      argv + 1, argv + argc, back_inserter(xs),
      [](char const *i) { return std::make_unique<X>(X{i}); });

  for (X const &v : get_iter(xs)) {
    std::cout << v._id << std::endl;
  }
}

Prints

a
a

BONUS std::ranges

  auto get_iter(Ptrs const &in) {
    using namespace std::ranges::views;
 return in | transform([](Ptr const &e) { return *e; }) |
           filter([](X const &e) { return e._id == "a"; });
  }

Or, equivalently

  auto get_iter(Ptrs const &in) {
    using namespace std::ranges::views;
 return in | transform([](Ptr const &e) { return *e; }) |
           filter([](X const &e) { return e._id == "a"; });
  }

See it live as well: http://coliru.stacked-crooked.com/a/8bbfff70772b3e02

sehe
  • 374,641
  • 47
  • 450
  • 633