问题
Does the following invoke undefined behavior?
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <experimental/iterator>
int main() {
long double values[] = {1, 2, 3};
std::transform(
std::begin(values), std::end(values),
std::experimental::make_ostream_joiner(std::cout, ", "),
[](long double v) {
return std::put_money(v + 1);
}
);
return 0;
}
My worry is that return std::put_money(v + 1)
returns a reference to the temporary v + 1
.
回答1:
The standard ([ext.manip]/6) only defines this specific expression:
out << put_money(mon, intl);
It is unspecified how mon
is stored in the mean time, and it is definitely possible for it to become a dangling reference and be UB.
An "easy" fix is making your own class to know you store the value:
struct money_putter {
long double value;
template<class charT, class traits>
friend std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& os, const money_putter& mon) {
return os << std::put_money(mon.value);
}
};
int main() {
int values[] = {1, 2, 3};
std::transform(
std::begin(values), std::end(values),
std::experimental::make_ostream_joiner(std::cout, ", "),
[](int i) {
return money_putter{i}; // or i + 1
}
);
return 0;
}
回答2:
You could test it, though this wont tell you anything about whether it is guaranteed, but then as the return type of put_money is not specified, you cannot assume that the returned value does not hold a reference.
...anyhow let's test it:
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <experimental/iterator>
int main() {
int i = 42;
std::cout << std::put_money(i) << "\n";
auto x = std::put_money(i);
i = 43;
std::cout << x;
return 0;
}
Output with clang:
42
43
So actually the answer is positive. With clang the returned value does hold a reference and the output is the same with gcc. Hence, yes your code has UB.
回答3:
This answer does a great job at answering my question, but I thought I'd supply a more generic solution to the problem of ensuring the object output to an ostream_joiner
takes no dangling references, one that uses a lambda to capture those references:
#include <type_traits>
#include <ostream>
template<typename F>
class put_invocation_t {
public:
constexpr put_invocation_t(F const& f) : f(f) {}
constexpr put_invocation_t(F&& f) : f(std::move(f)) {}
template<class charT, class traits>
friend std::basic_ostream<charT, traits>& operator<<(
std::basic_ostream<charT, traits>& os, put_invocation_t const& pi
) {
return pi.f(os);
}
private:
F f;
};
// or use a deduction guide in C++17
template<typename F>
put_invocation_t<std::decay_t<F>> put_invocation(F&& f) {
return put_invocation_t<std::decay_t<F>>(std::forward<F>(f));
}
Used as
std::transform(
std::begin(values), std::end(values),
std::experimental::make_ostream_joiner(std::cout, ", "),
[](long double v) {
return put_invocation([=](auto& os) -> auto& {
return os << std::put_money(v + 1);
});
}
);
This has the bonus of also scaling to outputing multiple values, by using something like the following within the transform
:
return put_invocation([=](auto& os) -> auto& {
return os << "Value is: " << std::put_money(v + 1);
});
来源:https://stackoverflow.com/questions/56499120/does-put-money-hold-its-argument-by-value-or-reference