Put_money содержит свой аргумент по значению или по ссылке? - PullRequest
7 голосов
/ 07 июня 2019

Вызывает ли следующее неопределенное поведение?

#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.

Ответы [ 3 ]

6 голосов
/ 07 июня 2019

Стандарт ( [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;
}
2 голосов
/ 07 июня 2019

Вы можете проверить это, хотя это ничего не скажет о том, гарантировано ли это, но тогда, поскольку тип возвращаемого значения 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;
}

Вывод с clang :

42
43

Так что на самом деле ответ положительный.С помощью clang возвращаемое значение содержит ссылку, и результат такой же, как у gcc .Следовательно, да, ваш код имеет UB.

0 голосов
/ 09 июня 2019

Этот ответ отлично справляется с ответом на мой вопрос, но я подумал, что предоставлю более общее решение проблемы обеспечения вывода объекта на ostream_joiner без висячих ссылок, один который использует лямбду для захвата этих ссылок:

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