我之前问过这个问题。现在我已将其扩展到以下示例中所示的实际用例(带有链接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();
}
这再次导致
std::cout << add << std::endl;
处出现段错误。由于它在 fmt::join
内部没有 foo
的情况下工作,我怀疑 fmt::joi
n 不能很好地应对我所得到的。
关于问题是什么以及如何解决的任何想法
那是因为这个坏了:
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); }), ",");
}
C++20(以及之前的 range-v3)具有
view
的广义概念,它不仅仅是迭代器/哨兵对。它也可以有任意状态。对于 views::transform
,该状态包括函数对象(当然还有底层的
view
)。问题是,
fmt::join
- 虽然它看起来像其他系列机械,但实际上并非如此。它只是做这个:
template <typename Range>
auto join(Range&& range, string_view sep)
-> join_view<decltype(detail::adl::adlbegin(range)),
decltype(detail::adl::adlend(range))> {
return join(detail::adl::adlbegin(range), detail::adl::adlend(range), sep);
}
也就是说 - 它只是从范围中取出 begin()
和
end()
并存储它们,扔掉范围的其余部分。这对于某些范围(特别是
借用范围)来说是可以的,但不是全部。具有状态转换的 views::transform
(如您此处所示)不是借用的范围 - 您需要在某处保留该可调用对象,而这在这里不会发生。因此出现了问题。 值得注意的是,这只是一个问题,因为
views::transform
实际上在使用之前就被破坏了。如果您在完整的管道构造中使用
fmt::join
,它会工作得很好:
auto f = fmt::format("{}", fmt::join(d | std::views::transform([this](int i) {
std::cout << add << std::endl;
return add + i;
}), ","));
因为临时 transform
直到完整表达式结束(即所有格式化发生之后)才会被销毁。一般来说,最好只使用这样的
fmt::join
。理想情况下,我们只需扩展范围格式说明符以包含分隔符,这样我们就完全不需要
fmt::join
。