Search code examples
c++11ofstream

Can't we manage std::map<string,ofstream>?


I tried to create for outputting the timing results and call any ofstream from pre-defined string:

#include <cstring>                                                                                                                  
#include <map>
#include <fstream>

using namespace std;

int main(void) {
    map<string,ofstream> Map;
    ofstream ofs("test_output");
    string st="test";
    Map[st] = ofs;
    return 0;
}

I got the following error; how can I fix it?

a.cpp: In function ‘int main()’:
a.cpp:11:8: error: use of deleted function ‘std::basic_ofstream<_CharT, _Traits>& std::basic_ofstream<_CharT, _Traits>::operator=(const std::basic_ofstream<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]’
  Map[s]=ofs;
        ^
In file included from a.cpp:3:0:
/usr/include/c++/5/fstream:744:7: note: declared here
   operator=(const basic_ofstream&) = delete;

       ^
In file included from a.cpp:3:0:
/usr/include/c++/5/fstream:744:7: note: declared here
   operator=(const basic_ofstream&) = delete; 

Solution

  • As an std::ostream is not copyable (copy constructor and assignment operator are marked deleted), you have to either construct the ofstream directly in the map (e.g. using std::map::emplace()) or use move assignment.

    Construct in-place

    There are basically two ways, either default-construct stream in the map (pre C++11), or call std::map::emplace() to supply ofstream constructor arguments.

    Using default-construction (works even pre C++11):

    map<string,ofstream> m;
    
    // default-construct stream in map    
    ofstream& strm = m["test"];
    strm.open("test_output");
    strm << "foo";
    

    Using emplacement:

    // 1st parameter is map key, 2nd parameter is ofstream constructor parameter    
    auto res = m.emplace("test", "test_output");
    auto& strm = res.first->second;
    strm << "bar";
    

    Move assignment

    We can construct the stream outside of the map first, turn it into an rvalue by calling std::move() and use move assignment operator to move it into the map:

    map<string,ofstream> m;    
    ofstream strm("test_output");
    m["test"] = std::move( strm );
    // strm is not "valid" anymore, it has been moved into the map
    

    We can even get rid of std::move() if we directly create the stream as an rvalue:

    m["test"] = ofstream("test_output");
    

    Move assignment is less efficient than the other methods, because first a stream will be default-constructed in the map, just to be replaced by the move-assigned stream then.

    Live demo of all three methods.

    Note: Sample code omitts any error handling for brevity. State of stream should be checked after opening and after each stream operation.