put_money 是按值还是按引用保存其参数?
Does put_money hold its argument by value or reference?
以下是否调用了未定义的行为?
#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;
}
我担心的是 return std::put_money(v + 1)
returns 对临时 v + 1
的引用。
标准([ext.manip]/6)只定义了这个特定的表达式:
out << put_money(mon, intl);
同时mon
是如何存储的还未明确,绝对有可能成为悬空引用成为UB
"easy" 修复是让您自己 class 知道您存储的值:
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;
}
你可以测试它,尽管这不会告诉你任何关于它是否有保证的信息,但是由于未指定 return 类型的 put_money,你不能假设 returned 值不包含引用。
...无论如何让我们测试一下:
#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;
}
输出 clang:
42
43
所以实际上答案是肯定的。使用 clang,returned 值确实包含一个引用,并且输出与 gcc 相同。因此,是的,您的代码有 UB。
很好地回答了我的问题,但我想我会提供一个更通用的解决方案来解决确保 ostream_joiner
的对象输出没有悬挂引用的问题,一个使用 lambda 来捕获那些引用的:
#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));
}
用作
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);
});
}
);
通过在 transform
:
中使用类似下面的内容,这还可以扩展到输出多个值。
return put_invocation([=](auto& os) -> auto& {
return os << "Value is: " << std::put_money(v + 1);
});
以下是否调用了未定义的行为?
#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;
}
我担心的是 return std::put_money(v + 1)
returns 对临时 v + 1
的引用。
标准([ext.manip]/6)只定义了这个特定的表达式:
out << put_money(mon, intl);
同时mon
是如何存储的还未明确,绝对有可能成为悬空引用成为UB
"easy" 修复是让您自己 class 知道您存储的值:
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;
}
你可以测试它,尽管这不会告诉你任何关于它是否有保证的信息,但是由于未指定 return 类型的 put_money,你不能假设 returned 值不包含引用。
...无论如何让我们测试一下:
#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;
}
输出 clang:
42
43
所以实际上答案是肯定的。使用 clang,returned 值确实包含一个引用,并且输出与 gcc 相同。因此,是的,您的代码有 UB。
ostream_joiner
的对象输出没有悬挂引用的问题,一个使用 lambda 来捕获那些引用的:
#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));
}
用作
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);
});
}
);
通过在 transform
:
return put_invocation([=](auto& os) -> auto& {
return os << "Value is: " << std::put_money(v + 1);
});