2984. put_money(99) is unnecessarily undefined

Section: 31.7.8 [ext.manip] Status: New Submitter: Jonathan Wakely Opened: 2017-06-22 Last modified: 2017-06-26

Priority: 3

View all other issues in [ext.manip].

View all issues with New status.

Discussion:

[ext.manip] p5 says:

Requires: The type moneyT shall be either long double or a specialization of the basic_string template (Clause 24).

This means that put_money(99), put_money(99.), put_money("99"), and put_money(string_view{"99"}) are all undefined, when in practice they will compile fine and do the right thing, converting the argument to long double or std::string as needed.

We could change it to be "otherwise the program is ill-formed", or to remove the function templates from overload resolution when the argument is not long double or a std::basic_string, but that will unnecessarily break code that works fine today. We should accept types convertible to long double or the relevant money_put facet's string_type (which is not known until we attempt to write the unspecified type to an ostream).

The requirement is also insufficient, because cout << put_money(wstring(L"99")) won't compile on any implementation, despite the argument type being a specialization of basic_string. This same problem exists for std::get_money.

[2017-06-24, Daniel comments and provides wording]

The wording changes below are supposed to support all moneyT types that are convertible to either long double or to money_put/get<Ch, o/istreambuf_iterator<Ch, Tr>>::string_type (but not to both), where Ch and Tr are determined by the concrete instantiated specialization of the exposition-only function template f that is used to specify the semantics of put_money and get_money, respectively. XOR-ing the requirements outlaws types that are convertible to both, which would cause an ambiguity unless we would provide wording that would introduce an ordered application of these convertibility constraints. This is the rationale for the seemingly odd new Remarks formulation. Note also, that the wording provided below intentionally attempts to distinguish between the statically testable conditions based on the is_convertible_v expressions within the Remarks: element and the well-defined runtime behaviour requirement of the actually provided argument of deduced type moneyT within the pre-existing Requires: element. Another point worth pointing out is that the wording attempts to fix an currently existing ambiguity of the meaning of the type moneyT (and to a lesser extend for charT and traits) as either the template parameter of put/get_money or that of the corresponding template argument of the exposition-only f templates. The revised form makes it clearer that it refers to the latter.

It should be emphasized that this extension of the current wording would provide support for put_money(99), put_money(99.), and put_money("99"), but not yet for put_money(string_view{"99"}), because string_view is not convertible to string. To realize support for the latter, this wording approach could be extended by referring to is_constructible instead of is_convertible, though.

Proposed resolution:

This wording is relative to N4659.

  1. Edit 31.7.8 [ext.manip] as indicated:

    template <class moneyT> unspecified get_money(moneyT& mon, bool intl = false);
    

    -?- For an expression in >> get_money(mon, intl) described below, let Mo, Ch, and Tr be the deduced template argument types of the template parameters moneyT, charT, and traits, respectively, of the instantiated specialization of the template f.

    -2- Requires: The type moneyT shall be either long double or a specialization of the basic_string template (Clause 27 [strings])Mo shall be either convertible to long double or shall be convertible to money_get<Ch, istreambuf_iterator<Ch, Tr>>::string_type.

    -?- Remarks: If is_convertible_v<Mo, long double> == is_convertible_v<Mo, money_get<Ch, istreambuf_iterator<Ch, Tr>>::string_type>, the program is ill-formed.

    -3- Effects: The expression in >> get_money(mon, intl) described below behaves as a formatted input function (31.7.5.3.1 [istream.formatted.reqmts]).

    -4- Returns: An object of unspecified type such that if in is an object of type basic_istream<charTCh, traitsTr> then the expression in >> get_money(mon, intl) behaves as if it called f(in, mon, intl), where the function f is defined as:

    template <class charT, class traits, class moneyT>
    void f(basic_ios<charT, traits>& str, moneyT& mon, bool intl) {
      using Iter = istreambuf_iterator<charT, traits>;
      using MoneyGet = money_get<charT, Iter>;
      ios_base::iostate err = ios_base::goodbit;
      const MoneyGet& mg = use_facet<MoneyGet>(str.getloc());
      mg.get(Iter(str.rdbuf()), Iter(), intl, str, err, mon);
      if (ios_base::goodbit != err)
        str.setstate(err);
    }
    

    The expression in >> get_money(mon, intl) shall have type basic_istream<charTCh, traitsTr>& and value in.

    template <class moneyT> unspecified put_money(const moneyT& mon, bool intl = false);
    

    -?- For an expression out << put_money(mon, intl) described below, let Mo, Ch, and Tr be the deduced template argument types of the template parameters moneyT, charT, and traits, respectively, of the instantiated specialization of the template f.

    -5- Requires: The type moneyT shall be either long double or a specialization of the basic_string template (Clause 27 [strings])Mo shall be either convertible to long double or shall be convertible to money_put<Ch, ostreambuf_iterator<Ch, Tr>>::string_type.

    -?- Remarks: If is_convertible_v<Mo, long double> == is_convertible_v<Mo, money_put<Ch, ostreambuf_iterator<Ch, Tr>>::string_type>, the program is ill-formed.

    -6- Returns: An object of unspecified type such that if out is an object of type basic_ostream<charTCh, traitsTr> then the expression out << put_money(mon, intl) behaves as a formatted output function (31.7.6.3.1 [ostream.formatted.reqmts]) that calls f(out, mon, intl), where the function f is defined as:

    template <class charT, class traits, class moneyT>
    void f(basic_ios<charT, traits>& str, const moneyT& mon, bool intl) {
      using Iter = ostreambuf_iterator<charT, traits>;
      using MoneyPut = money_put<charT, Iter>;
      const MoneyPut& mp = use_facet<MoneyPut>(str.getloc());
      const Iter end = mp.put(Iter(str.rdbuf()), intl, str, str.fill(), mon);
      if (end.failed())
        str.setstate(ios::badbit);
    }
    

    The expression out << put_money(mon, intl) shall have type basic_ostream<charTCh, traitsTr>& and value out.