[cfe-dev] [libc++] Should IO manipulators be noncopyable?
Jim Porter
jvp4846 at g.rit.edu
Sat Jul 5 15:05:04 PDT 2014
Recently, I was trying out the new std::quoted IO manipulator and came
across an interesting issue: since libc++'s std::quoted is copyable (as
are all the other IO manipulators I looked at), passing a temporary as
the first argument introduces a lifetime bug in the following code:
auto ensure_printable(const std::wstring &s) {
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> conv;
return std::quoted(conv.to_bytes(s));
}
// ...
std::cout << ensure_printable(std::wstring(L"hi"));
In C++98, it wasn't possible to do something like this without making
obviously non-standard code (i.e. explicitly writing out the
implementation-defined named for the return type of std::quoted()). In
C++11, and even moreso in C++14, this is no longer necessary thanks to
the `auto` keyword. Hence, (mostly) innocent-looking code like above can
introduce subtle bugs.
Obviously, since the return type of std::quoted() is unspecified, the
above code relies on undefined behavior: there's no guarantee in the
standard that it's copyable in the first place! After some discussion,
this got me to thinking about why any of the IO manipulators are
implemented as copyable types; presumably, this is mostly because it
didn't matter back in C++98 when most of them were introduced. However,
with deduced return types, this matters quite a bit more.
If the IO manipulators were written to return a const version of their
proxy type, and said type were move-only, I think this would resolve the
issue, and only allow the IO manipulators to be used as temporaries
(much like they were in C++98). Something like so:
struct my_manipulator_proxy {
my_manipulator_proxy(/* ... */) {}
my_manipulator_proxy(const my_manipulator_proxy &) = delete;
my_manipulator_proxy(my_manipulator_proxy &&) = default;
/* ... */
};
std::ostream & operator << (std::ostream &s,
const my_manipulator_proxy &m) {
/* ... */
}
const my_manipulator_proxy my_manipulator(/* ... */) {
return my_manipulator_proxy(/* ... */);
}
Having looked at the implementation of std::quoted, this seems like an
uncomplicated change (I imagine it's so for the other manipulators as
well), and helps prevent undefined behavior. Does this sound like a
reasonable way to go? I'd be happy to write a patch if others agree that
this is sensible.
- Jim
More information about the cfe-dev
mailing list