I previously asked this question. Now I have extended it to an actual use case shown in the following example (with link here):
#include <ranges>
#include <map>
#include <functional>
#include <fmt/format.h>
#include <iostream>
template<typename RANGE, typename LAM>
auto foo(RANGE&& range, LAM&& lambda) {
return fmt::join(range | std::views::transform(
[lam = std::forward<LAM>(lambda)](const auto& element) { return lam(element); }), ",");
}
class boo {
public:
int add {2};
void call_foo(){
std::vector<int> d { 2,3,4,5,6};
auto f = fmt::format("{}", foo(d, [this](int i) {
std::cout << add << std::endl;
return add + i;
}));
std::cout << f << std::endl;
};
};
int main() {
std::cout << "HELLO" << std::endl;
boo b;
b.call_foo();
}
This again results in a segfault at std::cout << add << std::endl;. Since it works without the fmt::join inside foo, I am suspecting that fmt::join does not cope well with what I got.
Any ideas to what the problem is and how it can be resolved
That's because this is broken:
C++20 (and range-v3 before it) has this generalized concept of
view, which isn't just an iterator/sentinel pair. It can also have arbitrary state. Forviews::transform, that state includes the function object (and of course the underlyingview).The problem is,
fmt::join- while it looks like the other range machinery, really isn't. It just does this:That is - it's just pulling out
begin()andend()from the range and storing those, throwing away the rest of the range.This is okay for some ranges (notably, borrowed ranges), but not all. A
views::transformwith a stateful transformation (as you have here) is not a borrowed range - you need to hold onto that callable somewhere, and this isn't happening here. Hence the problems: we're trying to invoke a function object that has already been destroyed (actually one level past that - we're trying to invoke a function object that is a member of another object that has already been destroyed, through a [dangling] pointer to that parent object).Notably, this is only a problem because the
views::transformactually is destroyed before it is used. If you usedfmt::joininline with the full pipeline construction, it'd work fine:Because the temporary
transformisn't destroyed until the end of the full-expression, which is after all the formatting happens.In general, it's best to only use
fmt::joinlike this. And ideally we simply extend the range formatting specifiers to include a delimiter so that we just obviate the need forfmt::joinentirely.