Yes, this is by design. Like the comments say, the adaptors store a reference to a temporary. This means the temporary is destructed after creating the adaptor, and the adaptor contains a stale reference.
To make it easier to understand, here's a similar adaptor (without using boost):
template <typename R> struct demo_adaptor {
R const& r;
auto begin() const { return r.begin(); }
auto end() const { return r.end(); }
};
Given a helper function to adapt a range r
:
template <typename R>
demo_adaptor<R> demo_adapt(R const& r) {
return { r };
}
The following program invokes Undefined Behaviour:
for(auto v : demo_adapt(fn())) {
std::cout << ' ' << v;
}
Note: Use -fsanitize=undefined,address
on GCC/Clang compilers to find such errors:

Because it gets interpreted as the following equivalent loop (Live On CppInsights):
{
demo_adaptor<std::vector<int, std::allocator<int>>>&& __range1 = demo_adapt(fn());
__gnu_cxx::__normal_iterator<const int*, std::vector<int, std::allocator<int>>> __begin1 = __range1.begin();
__gnu_cxx::__normal_iterator<const int*, std::vector<int, std::allocator<int>>> __end1 = __range1.end();
for (; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
int v = __begin1.operator*();
std::operator<<(std::cout, ' ').operator<<(v);
}
}
Now the easy fix is to ensure that the range lives longer than the adaptor will:
{
auto r = fn();
for (auto v : demo_adapt(r)) {
std::cout << ' ' << v;
}
}
Which prints
1 2 3