put_money是否按值或引用保存其参数

Does put_money hold its argument by value or reference?

本文关键字:引用 保存 参数 money 是否 put      更新时间:2023-10-16

以下行为是否调用未定义的行为?

#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)返回对临时v + 1的引用。

标准 ([ext.manip]/6( 仅定义了以下特定表达式:

out << put_­money(mon, intl);
目前

还未指定mon如何同时存储,并且绝对有可能成为悬而未决的引用并成为UB。

一个"简单"的解决方法是创建自己的类来知道您存储值:

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;
}

您可以对其进行测试,尽管这不会告诉您有关它是否得到保证的任何信息,但是由于未指定put_money的返回类型,因此不能假设返回值不包含引用。

。无论如何,让我们测试一下:

#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;
}

叮当声输出:

42
43

所以实际上答案是肯定的。使用 clang 时,返回的值确实包含引用,并且输出与 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);
});