Boost logo

Boost :

Subject: Re: [boost] [Range] New I/O functions for ranges
From: Indiana (indi.in.the.wired_at_[hidden])
Date: 2014-10-15 19:38:37


As an update of what I've decided on doing: Following the advice of Paul
Bristow and Rob Stewart, I have decided to implement the range I/O stuff
as a new library with Boost.Range as a dependency. Once it's all up and
running and tested and such, I can ask Boost.Range's maintainers whether
they'd like to absorb it into Boost.Range or leave it as a separate
library (Boost.RangeIO?). Since the whole thing is already (more or
less) implemented as a standards proposal, all I had to do, really, was
just change the namespace from std to boost, and some other housekeeping
stuff. However, I decided to take the opportunity to examine a
possibility that a couple people have asked about: whether or not these
C++11 facilities can be made available to C++98 code. Looking into that
possibility is what's kept me occupied recently.

That's where I hit a little snag.

It turns out is probably *possible* to implement the facilities in
C++98... however, it is impossible to do so in a way that guarantees
safety. The problem is that C++98 has no way to prevent you from passing
a temporary to a function except by taking the argument by non-const
(lvalue) reference, which is too restrictive to help in this case.

The following code is perfectly safe in C++11:

std::vector<int> get_vec();

// as lvalue
std::vector<int> const v = get_vec();
write_all_t<std::vector<int>, std::string> p = write_all(v, ", ");
// p contains a reference to v

// as rvalue
write_all_t<std::vector<int>, std::string> p = write_all(get_vec(), ", ");
// p owns the vector

But in C++98 it takes a reference to the vector in both cases, which is
no good in the second case because the reference is dangling by the end
of the expression.

The only way to prevent binding to temporaries in C++98 is to take
arguments by non-const (lvalue) reference, but if write_all() does that
then the "as lvalue" code above won't work.

Boost.Move emulation doesn't help in this case, unfortunately. (See the
section "Emulation limitations". The relevant subsections are "Template
parameters for perfect forwarding" and "Binding of rvalue references to
lvalues".)

There is no way that the interface or design can be changed to avoid
this problem and still remain even marginally useful in practice - the
whole concept arose out of an observation of what rvalue references
would now allow us to do safely.

So my options come down to this:
1) Declare the library C++11-or-better-only. That can be enforced by
ensuring that BOOST_NO_CXX11_RVALUE_REFERENCES is not defined, and
issuing an error message otherwise.
2) Allow the library to be used in C++98 code, with a warning - either
in documentation or, less than ideally, as a diagnostic - about
temporaries. Note that there is no way to detect or diagnose if the
warning is ignored, deliberately or accidentally, so things that are
perfectly legal in C++11 mode can silently introduce undefined behaviour
in C++98 mode.

I'm leaning toward option 1, mainly for the reason that this is a
"syntactic sugar" library. It is not based on any non-portable
facilities - anyone can reimplement it on any conforming system - so it
doesn't provide "new" capability so much as it makes "old" capability
*much* easier. We got by without it for so long in C++98 that it's
obviously not the kind of thing people are desperate for - it's more a
really nice way to simplify common code, which to me implies it's the
kind of thing you will happily use if you can, but it won't cripple your
project if you can't. For a facility like that, I don't think it's a
worthwhile to introduce potentially undetectable bugs. Better to say "if
you can't use it safely, you can't use it - sorry, but just consider
this another motivation to move to C++11 when possible", which may irk
those developers who can't move to C++11, but c'est la vie.

I haven't found any official policy that *disallows*
C++11-or-better-only libraries (in fact, I stumbled on someone forking
Boost to make it *completely* C++11-or-better-only), and it seems to me
that relying on it is no different from a library relying on C++98
features that some compilers never got around to supporting. Obviously,
insisting on C++11 just for the sake of doing so would be silly, but in
this case the library *needs* a C++11 feature that cannot be emulated or
avoided (indeed, safe rvalue reference binding is just about the only
C++11 feature that falls into that category) - and the functionality it
provides is (IMHO) well worth it.

So unless anyone objects, I'm going to go ahead and make this "RangeIO"
library require C++11. (Specifically, I'm going to make it require
rvalue references - I'm not going to use any more C++11 functionality
than is absolutely necessary, so no auto or non-static member
initializers, etc.. For example, I will have it use
std::initializer_list, but only if BOOST_NO_CXX11_HDR_INITIALIZER_LIST
is not defined.)


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk