Workaround for returning uncopyable object without a move ctor

849 Views Asked by At

In my API I have a function that returns std::istringstream.
The std::istringstream class is non-copyable but supports moving so on a conforming compiler there is no problem returning a local std::istringstream.

However, on gcc 4.9, there is no support for moving std::istringstream. Is there some workaround that I can use that std::istringstream without changing the API from the user's perspective?

The workaround suggested here, of using a unique_ptr<std::istringstream> will change the semantics of the API.

3

There are 3 best solutions below

0
Adi Shavit On BEST ANSWER

Answering my own question for completeness and future reference.

The goal was to find a workaround for the gcc (< 5) bug where std::istringstream does not provide a move ctor that will work in cases where I want to return the un-copyable and (bugly-) unmovable stream.
As mentioned in the comments, I can in fact change my function signature (at least on gcc < 5) to return a proxy object that allows copying or moving without changing the API for code used on newer/other compilers.

The idea, suggested and implemented by a colleague, is to create a proxy object around std::istringstream which provides a similar API, but also provides a copy-ctor which manually creates and initializes a new internal std::istringstream from the copied-from stream. This proxy is used only on the offending compilers.

The code in its natural habitat is here.
Here's the relevant part:

#if !defined(__GNUC__) || (__GNUC__ >= 5)
   using string_stream = std::istringstream;
#else
    // Until GCC 5, istringstream did not have a move constructor.
    // stringstream_proxy is used instead, as a workaround.
   class stringstream_proxy
   {
   public:
      stringstream_proxy() = default;

      // Construct with a value.
      stringstream_proxy(std::string const& value) :
         stream_(value)
      {}

      // Copy constructor.
      stringstream_proxy(const stringstream_proxy& other) :
         stream_(other.stream_.str())
      {
         stream_.setstate(other.stream_.rdstate());
      }

      void setstate(std::ios_base::iostate state) { stream_.setstate(state); }

      // Stream out the value of the parameter.
      // If the conversion was not possible, the stream will enter the fail state,
      // and operator bool will return false.
      template<typename T>
      stringstream_proxy& operator >> (T& thing)
      {
         stream_ >> thing;
         return *this;
      }


      // Get the string value.
      std::string str() const { return stream_.str(); }

      std::stringbuf* rdbuf() const { return stream_.rdbuf(); }

      // Check the state of the stream. 
      // False when the most recent stream operation failed
      operator bool() const { return !!stream_; }

      ~stringstream_proxy() = default;
   private:
      std::istringstream stream_;
   };
   using string_stream = stringstream_proxy;
#endif
0
Guillaume Racicot On

If you can't move the std::istringstream, there's no much way around it.

If an object is non copiable and non movable, you can't return it by value. If you want to support new features, you better get a new compiler for those.

In the meatime, you could return a unique_ptr. If you're really eager to return by value, you could return a movable wrapper that contains a std::unique_ptr<std::istringstream> and provide the same interface as a istringstream. However, this also affect the return type.


It may be tempting to return by rvalue reference. Here's what you can do:

struct MyApiClass {

    std::istringstream&& get_stream() {
        return std::move(*_stream);
    }

private:
    std::unique_ptr<std::istringstream> _stream;
};

Then, with your old compiler, you can use it like this:

std::istringstream&& stream = myApiClass.get_stream();

// use stream as long as myApiClass exists

People using a new compiler will be able to use it like that:

std::istringstream stream = myApiClass.get_stream();

// use stream normally

This is the way the api is less affected. Other than that, I don't know any workaround.

0
Jarod42 On

The way to return class without move/copy constructor is to use the return statement with braced-init-list:

class C {
    C() = default;
    C(const C&) = delete;
    C(C&&) = delete;
};

C make_C() { return {}; }

int main() {
    C&& c = make_C();
}

Demo

Unfortunately, only non-explicit constructor are considered for this initialization and std::istringstream have explicit constructor.

One workaround is to create a sub-class with non explicit constructor:

struct myIStringStream : std::istringstream
{
    myIStringStream () = default;
};

myIStringStream make_istringstream()
{
    return {};
}

int main()
{
    std::istringstream&& iss = make_istringstream();
}

Demo