Łukasz Przeniosło
Łukasz Przeniosło

Reputation: 2949

cannot use operator= with std::stringstream

I am trying to make a struct, in which one of the members is of std::stringstream type. I am using C++11, and according to http://www.cplusplus.com/reference/sstream/stringstream/operator=/ I can do it.

Here is my code:

struct logline_t
    {
        stringstream logString; /*!< String line to be saved to a file (and printed to cout). */
        ElogLevel logLevel; /*!< The \ref ElogLevel of this line. */
        timeval currentTime; /*!< time stamp of current log line */

        logline_t& operator =(const logline_t& a)
        {
            logString = a.logString;
            logLevel = a.logLevel;
            currentTime = a.currentTime;

            return *this;
        }
    };

It doesn't compile, as I am getting this error:

error: use of deleted function ‘std::basic_stringstream<char>& std::basic_stringstream<char>::operator=(const std::basic_stringstream<char>&)’

I don't understand why it doesn't work. I have tried logString = move(a.logString); as well. Same result. I would appreciate all help.

Edit: Here is my code, I have applied the changes suggested by most of the users and in my code they do not compile. I am still getting an error at the very beginning of the struct.

CLogger.h

Line 40: ../src/CLogger.h:40:9: error: use of deleted function ‘std::basic_stringstream<char>::basic_stringstream(const std::basic_stringstream<char>&)’

CLogger.cpp

Line 86: ../src/CLogger.cpp:86:41: error: use of deleted function ‘CLogger::logline_t::logline_t(const CLogger::logline_t&)’

Line 91: ../src/CLogger.cpp:91:9: error: use of deleted function ‘CLogger::logline_t::logline_t(const CLogger::logline_t&)’

If any other information is needed i will provide it.

Upvotes: 10

Views: 6737

Answers (4)

Anton Savin
Anton Savin

Reputation: 41321

std::stringstream is not copyable. To copy the content you can just write the content of one stream to another:

logString << a.logString.str();

Update: Also if you don't follow a good advice to implement operator= with copy-and-swap idiom using copy constructor, you have to clear the stream first:

logString.str({});
logString << a.logString.str();

or just

logString.str(a.logString.str());

Also you may be tempted to use rdbuf() instead:

logString << a.logString.rdbuf();

but this is incorrect, because it alters the state of a.logString (despite that a.logString is const, a.logString.rdbuf() is a pointer to non-const object). This is demonstrated by the following code:

logline_t l1;
l1.logString << "hello";
logline_t l2, l3;
l2 = l1;
l1.logString << "world";
l3 = l1;
// incorrectly outputs: l2: hello, l3: world
// correct output is: l2: hello, l3: helloworld
std::cout << "l2: " << l2.logString.str() << ", l3: " << l3.logString.str() << std::endl;

Upvotes: 12

Galik
Galik

Reputation: 48635

The problem is you are trying to copy a std::stringstream object which is non copyable. You can get round this by not copying the object itself but copy its contents into the new std::stringstream.

Also you really need a copy constructor as well as the copy assignment operator. The need to provide both of these (and usually a destructor as well) is explained in the so-called Rule Of Three.

Also we need to add a default consructor because adding a copy constructor prevented the compiler from generating its own default constructor.

struct logline_t
{
    std::stringstream logString; /*!< String line to be saved to a file (and printed to cout). */
    ElogLevel logLevel; /*!< The \ref ElogLevel of this line. */
    timeval currentTime; /*!< time stamp of current log line */

    // default constructor needed because we made a copy constructor
    // which deleted the compiler generated default constructor!
    logline_t()
    : logLevel(0) // set suitable defaults
    {
        gettimeofday(&currentTime, 0); // for example
    }

    // copy constructor
    logline_t(const logline_t& ll)
    : logString(ll.logString.str()) // construct from contents .str()
    , logLevel(ll.logLevel)
    , currentTime(ll.currentTime)
    {
    }

    // copy assignment operator
    logline_t& operator=(const logline_t& a)
    {
        logString.str(a.logString.str()); // assign from contents
        logLevel = a.logLevel;
        currentTime = a.currentTime;

        return *this;
    }
};

Upvotes: 1

Dimitrios Bouzas
Dimitrios Bouzas

Reputation: 42929

Reason:

std::stringstream::operator= acquires the contents of its right hand side, by move-assigning its members and base classes.

In your overloaded operator= the input argument is const. Thus, the input argument's member logString cannot be moved. Additionally, the operator=(std::stringstream const&) in stringstream is declared deleted. Overload resolution chooses a deleted operator and rightfully you're getting a compile error.

Solution:

logline_t& operator =(const logline_t& a) {
  logString.str(a.logString.str());;
  logLevel = a.logLevel;
  currentTime = a.currentTime;
  return *this;
}

LIVE DEMO

Upvotes: 3

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145359

Streams are not copyable. But they are movable.

Still you could make your copy assignment operator work by just creating a suitable stringstream.

On the third hand it doesn't quite feel right to have a stream as a member? And if you really want that, why not use an ostringstream, why a stringstream? By reflecting on this, the design might be improved (and possibly that will the remove the current problem).


Example of workaround by creating a suitable stringstream (well, ostringstream), using the exception safe copy/swap idiom:

#include <sstream>
#include <utility>          // std::swap

namespace my {
    using std::ostringstream;
    using std::swap;

    struct S
    {
        ostringstream line;

        void swap_with( S& other ) noexcept
        {
            swap( line, other.line );
        }

        auto operator=( S const& other )
            -> S&
        {
            S temp( other );
            swap_with( temp );
            return *this;
        }

        S() = default;

        S( S const& other )
            : line( other.line.str() )
        {}
    };
}  // namespace my

auto main() -> int
{
    my::S o1, o2;
    o1 = o2;
}

Note that this relies on std::swap, which is specialized for ostringstream.


Example of a simpler but in principle not exception safe workaround:

#include <sstream>
#include <utility>          // std::swap

namespace my {
    using std::ostringstream;
    using std::swap;

    struct S
    {
        ostringstream line;

        auto operator=( S const& other )
            -> S&
        {
            line.str( other.line.str() );   // This can in theory throw, but.
            return *this;
        }

        S() = default;

        S( S const& other )
            : line( other.line.str() )
        {}
    };
}  // namespace my

auto main() -> int
{
    my::S o1, o2;
    o1 = o2;
}

Upvotes: 4

Related Questions