Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vector of streams in C++11

The following code

vector<ofstream> v;
v.emplace_back("file1.txt");
v.emplace_back("file2.txt");
for (int i = 0, ilen = v.size(); i < ilen; ++i)
    v[i] << "Test" << i << endl;

compiles fine in VS2013, but fails in GCC with unreadable message. It seems that the behaviour of VS2013 is correct.

  • I don't copy a stream, but create it in-place;
  • When vector gets big enough, contents should be moved to a new memory area.

Though I couldn't find a right place in standard that says something articulate on this. Could someone quote it, please?

like image 938
polkovnikov.ph Avatar asked Nov 30 '22 19:11

polkovnikov.ph


2 Answers

If you scroll down to the end of the errors thrown by clang you'll see this one:

/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/basic_ios.h:66:23: note: copy constructor of 'basic_ios<char, std::char_traits<char> >' is implicitly deleted because base class 'std::ios_base' has an inaccessible copy constructor

This is the corresponding line from gcc's long list of errors:

/usr/include/c++/4.8/bits/basic_ios.h:66:11: note: 'std::basic_ios<char>::basic_ios(const std::basic_ios<char>&)' is implicitly deleted because the default definition would be ill-formed:

     class basic_ios : public ios_base

This is because libstdc++ is missing move constructors for basic_ios, as listed here on the status page.

27.5 | Iostreams base classes | Partial | Missing move and swap operations on basic_ios.

And here's the associated bugzilla. Your code compiles with clang if you use libc++.

A simpler example, copied from the bug report, also fails to compile:

#include <sstream>
#include <utility>
std::stringstream getss(){
   std::stringstream q;
   return std::move(q);
}
like image 120
Praetorian Avatar answered Dec 03 '22 08:12

Praetorian


To fix your problem:

template<typename T, typename...Args>
std::unique_ptr<T> make_unique(Args&&...args) {
  return std::unique_ptr<T>( new T(std::forward<Args>(args)...) );
}

and if you hate typing ofstream:

 template<typename...Args>
 std::unique_ptr<std::ofstream> make_up_ofstream(Args&&...args) {
   return make_unique<std::ofstream>(std::forward<Args>(args)...);
 }

giving you:

std::vector<std::unique_ptr<std::ofstream>> v;
v.emplace_back(make_up_ofstream("file1.txt"));
v.emplace_back(make_up_ofstream("file2.txt"));
for (int i = 0, ilen = v.size(); i < ilen; ++i)
  *(v[i]) << "Test" << i << endl;

which is pretty close, no?

This makes me want to write make_up that deduces the type of the unique_ptr you are assigning it to:

// dense boilerplate, obsolete in C++1y:
template<unsigned...>struct indexes{typedef indexes type;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned Max>using make_indexes_t=typename make_indexes<Max>::type;

template<typename T>using type=T;

template<typename... Args>
struct up_maker {
  std::tuple<Args...> args;
  template<class T, class...Ds, unsigned...Is>
  std::unique_ptr<T,Ds...> helper( indexes<Is...> ) && {
    return std::unique_ptr<T,Ds...>( new T(std::forward<Args>( std::get<Is>(args) )...) );
  }
  template<class T, class...Ds>
  operator type<std::unique_ptr<T,Ds...>>() && {
    return std::move(*this).helper<T,Ds...>( make_indexes_t< sizeof...(Args) >{} );
  }
  explicit up_maker( Args&&... args_in ):args( std::forward<Args>(args_in)... ) {}
  up_maker( up_maker const& ) = delete;
  up_maker( up_maker && ) = default;
  up_maker& operator=( up_maker const& ) = delete;
  up_maker& operator=( up_maker && ) = default;
};

template<typename...Args>
up_maker<Args...> make_up( Args&&... args ) {
  return up_maker<Args...>( std::forward<Args>(args)... );
}

which if I wrote it right, gets rid of more boilerplate in your code:

std::vector<std::unique_ptr<std::ofstream>> v;
v.emplace_back(make_up("file1.txt"));
v.emplace_back(make_up("file2.txt"));
for (int i = 0, ilen = v.size(); i < ilen; ++i)
  (*v[i]) << "Test" << i << std::endl;

... lots of code just to get rid of two _ofstream, but it was amusing.

live example

like image 28
Yakk - Adam Nevraumont Avatar answered Dec 03 '22 09:12

Yakk - Adam Nevraumont