1010// basic_text_iprimitive.hpp
1111
1212// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
13+ // Copyright 2026 Gennaro Prota.
1314// Distributed under the Boost Software License, Version 1.0.
1415// (See accompanying file LICENSE_1_0.txt or copy at
1516// http://www.boost.org/LICENSE_1_0.txt)
2425// in such cases. So we can't use basic_ostream<IStream::char_type> but rather
2526// use two template parameters
2627
28+ #include < ios>
29+ #include < limits>
2730#include < locale>
2831#include < string>
2932#include < cstddef> // size_t
33+ #include < streambuf>
34+ #include < string>
3035
3136#include < boost/config.hpp>
3237#if defined(BOOST_NO_STDC_NAMESPACE)
@@ -39,6 +44,7 @@ namespace std{
3944#endif
4045
4146#include < boost/io/ios_state.hpp>
47+ #include < boost/mpl/bool.hpp>
4248#include < boost/static_assert.hpp>
4349
4450#include < boost/detail/workaround.hpp>
@@ -85,11 +91,17 @@ class BOOST_SYMBOL_VISIBLE basic_text_iprimitive {
8591 > locale_saver;
8692 #endif
8793
94+ // Whether a value of T can be an infinity or a NaN, and so may reach
95+ // us written as letters rather than as digits.
8896 template <class T >
89- void load (T & t)
90- {
91- if (is >> t)
92- return ;
97+ struct has_non_finite {
98+ typedef typename mpl::bool_<
99+ std::numeric_limits<T>::has_infinity
100+ || std::numeric_limits<T>::has_quiet_NaN
101+ >::type type;
102+ };
103+
104+ void throw_input_stream_error (){
93105 const std::string message =
94106 detail::stream_position_message (is.rdbuf ());
95107 boost::serialization::throw_exception (
@@ -100,6 +112,164 @@ class BOOST_SYMBOL_VISIBLE basic_text_iprimitive {
100112 );
101113 }
102114
115+ // Takes a leading sign, if there is one, and says whether it was a
116+ // minus.
117+ bool take_minus_sign (){
118+ typedef typename IStream::traits_type traits_type;
119+ typedef typename IStream::char_type char_type;
120+
121+ std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf ();
122+ if (NULL == sb){
123+ return false ;
124+ }
125+ is >> std::ws;
126+ const typename traits_type::int_type c = sb->sgetc ();
127+ if (traits_type::eq_int_type (c, traits_type::to_int_type (char_type (' -' )))
128+ || traits_type::eq_int_type (c, traits_type::to_int_type (char_type (' +' )))){
129+ return traits_type::eq_int_type (
130+ sb->sbumpc (), traits_type::to_int_type (char_type (' -' ))
131+ );
132+ }
133+ return false ;
134+ }
135+
136+ // Only the letters are taken, and not everything up to the next space,
137+ // because an XML archive ends a value with a tag. The terminator is
138+ // left where it is for the same reason. Lowered as it goes, since it is
139+ // ASCII either way.
140+ std::string take_letters (){
141+ typedef typename IStream::traits_type traits_type;
142+ typedef typename IStream::char_type char_type;
143+
144+ std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf ();
145+ std::string token;
146+ if (NULL == sb){
147+ return token;
148+ }
149+ for (;;){
150+ const typename traits_type::int_type c = sb->sgetc ();
151+ if (traits_type::eq_int_type (c, traits_type::eof ())){
152+ break ;
153+ }
154+ const char_type letter = traits_type::to_char_type (c);
155+ if (! ((char_type (' a' ) <= letter && letter <= char_type (' z' ))
156+ || (char_type (' A' ) <= letter && letter <= char_type (' Z' )))){
157+ break ;
158+ }
159+ token += char (char (letter) | 0x20 );
160+ sb->sbumpc ();
161+ }
162+ return token;
163+ }
164+
165+ // A NaN may carry a parenthesised payload, as in the "nan(ind)" the
166+ // Microsoft library writes, which has to come away with it.
167+ void take_nan_payload (){
168+ typedef typename IStream::traits_type traits_type;
169+ typedef typename IStream::char_type char_type;
170+
171+ std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf ();
172+ if (NULL == sb
173+ || ! traits_type::eq_int_type (
174+ sb->sgetc (), traits_type::to_int_type (char_type (' (' ))
175+ )){
176+ return ;
177+ }
178+ for (;;){
179+ const typename traits_type::int_type c = sb->sbumpc ();
180+ if (traits_type::eq_int_type (c, traits_type::eof ())
181+ || traits_type::eq_int_type (
182+ c, traits_type::to_int_type (char_type (' )' ))
183+ )){
184+ break ;
185+ }
186+ }
187+ }
188+
189+ template <class T >
190+ void take_non_finite (T & t, const std::string & token){
191+ if ((" inf" == token || " infinity" == token)
192+ && std::numeric_limits<T>::has_infinity){
193+ t = std::numeric_limits<T>::infinity ();
194+ return ;
195+ }
196+ if (" nan" == token && std::numeric_limits<T>::has_quiet_NaN){
197+ take_nan_payload ();
198+ t = std::numeric_limits<T>::quiet_NaN ();
199+ return ;
200+ }
201+ throw_input_stream_error ();
202+ }
203+
204+ // Before 2015, the Microsoft library wrote an infinity as "1.#INF", a NaN
205+ // as "1.#QNAN" and an indeterminate as "-1.#IND". The extraction takes
206+ // the leading "1." for the value and stops at the '#', so an archive
207+ // written then loads as 1 and the rest is left to derail the next read.
208+ // Nothing else puts a '#' after a number, so seeing one settles it.
209+ template <class T >
210+ void take_legacy_non_finite (T & t){
211+ typedef typename IStream::traits_type traits_type;
212+ typedef typename IStream::char_type char_type;
213+
214+ std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf ();
215+ if (NULL == sb
216+ || ! traits_type::eq_int_type (
217+ sb->sgetc (), traits_type::to_int_type (char_type (' #' ))
218+ )){
219+ return ;
220+ }
221+ sb->sbumpc ();
222+ const std::string token = take_letters ();
223+ if (" inf" == token && std::numeric_limits<T>::has_infinity){
224+ t = std::numeric_limits<T>::infinity ();
225+ }
226+ else if ((" qnan" == token || " snan" == token || " ind" == token)
227+ && std::numeric_limits<T>::has_quiet_NaN){
228+ t = std::numeric_limits<T>::quiet_NaN ();
229+ }
230+ else {
231+ throw_input_stream_error ();
232+ }
233+ }
234+
235+ template <class T >
236+ void load_impl (T & t, boost::mpl::bool_<false > &){
237+ if (is >> t)
238+ return ;
239+ throw_input_stream_error ();
240+ }
241+
242+ // An infinity and a NaN arrive as letters, which the extraction of a
243+ // floating point number refuses (issue #386). We look for the letters
244+ // before the extraction, not after it fails, because a failed extraction
245+ // may already have consumed some of them: libc++ takes "infini" out of
246+ // "infinity" before giving up. A number cannot begin with a letter, so
247+ // finding none means the extraction can go ahead.
248+ template <class T >
249+ void load_impl (T & t, boost::mpl::bool_<true > &){
250+ const bool negative = take_minus_sign ();
251+ const std::string token = take_letters ();
252+ if (! token.empty ()){
253+ take_non_finite (t, token);
254+ }
255+ else if (is >> t){
256+ take_legacy_non_finite (t);
257+ }
258+ else {
259+ throw_input_stream_error ();
260+ }
261+ if (negative){
262+ t = -t;
263+ }
264+ }
265+
266+ template <class T >
267+ void load (T & t)
268+ {
269+ typename has_non_finite<T>::type tag;
270+ load_impl (t, tag);
271+ }
272+
103273 void load (char & t)
104274 {
105275 short int i;
0 commit comments