[cfe-dev] [libc++] Should IO manipulators be noncopyable?

Jim Porter jvp4846 at g.rit.edu
Mon Jul 7 11:54:42 PDT 2014


In my sample implementation, I think they would need to be movable so 
that the wrapper function (my_manipulator) can move-construct its return 
value from the proxy object (my_manipulator_proxy). Then the wrapper 
makes sure that its return value is const so it can't be used by another 
move-constructor, effectively making it non-copyable and non-movable.

If you have a better idea for how to implement this, that's ok too, as 
long as the following are disallowed:

   auto foo() {
     return my_manipulator(/* ... */);
   }

   auto x = my_manipulator(/* ... */);

- Jim

On 7/7/2014 1:23 PM, David Blaikie wrote:
> +mclow
>
> do these even need to be movable?
>
> On Sat, Jul 5, 2014 at 3:05 PM, Jim Porter <jvp4846 at g.rit.edu> wrote:
>> 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
>>
>> _______________________________________________
>> cfe-dev mailing list
>> cfe-dev at cs.uiuc.edu
>> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-dev





More information about the cfe-dev mailing list