c++referenceostringstreamdefault-copy-constructor

Why does a member of ostringstream type in class cause "call to implicity deleted copy-constructor" error?


I have isolated a problem with "call to implicitly deleted copy-constructor" compilation errors to the use of an ostringstream type in declaring members of a class. In the example below, a STL list of objects of the example's Reading class is defined. At the point where push_back is called, the compiler searches for the copy-constructor and the compilation fails, seemingly because the copy constructor for Readings has been implicitly deleted.

When I comment out the two lines referring to payloadString, the program compiles.

I am thinking that my problem might be that ostringstream is of reference type, as explained here:

https://en.cppreference.com/w/cpp/language/copy_constructor "T has a data member of rvalue reference type;" is cited as one of the possible reasons that copy constructors are implicitly deleted.

Q's. Can anyone confirm if my above assumption about ostringstream being of reference type causing the problem is correct?

I am using ostringstream for reasons that are not really apparent in this contrived example. Maybe I need to find another way to handle this string, but can anyone please suggest an approach that will work here?

// testing a problem where ostringstream causes implicitly deleted copy constructor
//
// using ostringstream in a class definition seems to cause implicit deletion of the copy constructor

#include <iostream>
#include <sstream>
#include <list>
#include <string>

using namespace std;

class Reading {
    public:
        double elevation;
        std::ostringstream payloadString; // using ostringstream here causes implicit deletion of the copy constructor
        double speed;

    // constructors and member functions
        Reading();          // initialisation constructor declaration
    private:
    };

Reading::Reading(): // initialisation constructor definition
        elevation(0.0),
        payloadString("_null_null_"),  // commenting out this line and the previous definition in the class makes the problem go away
        speed(0.0)
        {}

int main()
{

    std::list<Reading> readingsList; // a list of readings

    Reading fakeReading; // just initialises with dummy data

    // this line is what causes the compiler to complain about implicitly deleted copy constructors
    readingsList.push_back(fakeReading);

    return 0;
}

Solution

  • Each class has an implicitly-declared copy constructor if you don't declare one, but only if each data member and inherited type can be copy-constructed. std::ostringstream has no copy constructor, therefore the compiler cannot generate a copy constructor for Reading.

    You can define a copy constructor yourself, if you can determine a way to construct Reading::payloadString in a meaningful way. For example, you could do something like:

    Reading(Reading const & other) :
        elevation{other.elevation},
        payloadString{other.payloadString.str()},
        speed{other.speed} { }
    

    Note that this copies the string value contained in other.payloadString but does not copy other aspects of the stream, such as its various output modes or its output position. This might be sufficient for your case.

    If you define this constructor, you may also want to define the copy-assignment operation, which cannot be automatically generated for the same reason. You could mimic the semantics of the copy constructor above:

    Reading & operator=(Reading const & other) {
        elevation = other.elevation;
        payloadString = std::ostringstream{other.payloadString.str()};
        speed = other.speed;
        return *this;
    }
    

    Note that std::ostringstream can be moved, which means the compiler will automatically generate a move constructor and a move-assignment operator for Reading. Therefore, you could just move-construct the list element from fakeReading:

    readingsList.emplace_back(std::move(fakeReading));
    

    If you do opt to implement the copy constructor/assignment then the compiler will not generate the move constructor/assignment for you, and you would have to explicitly tell the compiler to generate them:

    Reading(Reading &&) = default;
    Reading & operator=(Reading &&) = default;