3

I am using C++11 and trying to construct a class that owns a movable type like this:

class foo {
    std::istream input;

public:
    foo(std::istream && in): input(in) { }
};

And then instantiate the object:

foo var1(std::ifstream("/tmp/something"));

But the compiler always complains that I am calling a deleted constructor. Is this even possible?

clang++ -stdlib=libc++ -std=c++0x foo.cpp 
foo.cpp:7:30: error: call to deleted constructor of 'std::istream' (aka 'basic_istream<char>')
    foo(std::istream && in): input(in) { }
                             ^     ~~
/usr/bin/../lib/c++/v1/istream:1740:23: note: function has been explicitly marked deleted here
extern template class basic_istream<char>;
                      ^
1 error generated.
ildjarn
  • 62,044
  • 9
  • 127
  • 211
plaisthos
  • 6,255
  • 6
  • 35
  • 63
  • Could you post the compiler error? – hmjd Dec 21 '11 at 13:03
  • There are no copy constructors for streams the call input(in) is making a copy (since in is named it is no longer an rvalue reference). – mark Dec 21 '11 at 13:10

4 Answers4

9

Unfortunately you can not copy (or even move) the abstract stream type std::istream. You can hold them by reference, but that doesn't seem to be what your code wants to do.

As you are passing in a std::ifstream, I'll assume that this is the type of stream you really want to deal with. Unlike std::istream, std::ifstream is a concrete type. And this type is movable, though not copyable.

The answer hmjd had, and that you marked correct, is the best way to code this:

#include <fstream>

class foo
{
    std::ifstream input;
public:
    foo(std::ifstream&& in): input(std::move(in)) { }
};

Explanation: The foo constructor will only accept rvalue std::ifstreams, which is correct. But once you're inside this constructor, the variable in is no longer an rvalue (it is an lvalue). And thus you need to cast it back to an rvalue (using std::move) to move construct the data member input.

This is the same pattern that you would use with any move-only type (stringstream, unique_ptr, future, unique_lock, etc.).

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • This does not compile with gcc 4.6.1: In constructor 'foo::foo(std::ifstream&&)': error: use of deleted function 'std::basic_ifstream::basic_ifstream(const std::basic_ifstream&)' Perhaps this is due to incomplete support on GCC's behalf? – mark Dec 21 '11 at 16:57
  • 3
    @mark: Correct, this would be a gcc bug. `basic_ifstream(basic_ifstream&& rhs);` is specified in 27.9.1.7 [ifstream.cons] / p4: *Effects:* Move constructs from the rvalue `rhs`. ... – Howard Hinnant Dec 21 '11 at 17:29
  • your solution is eactly what I tried first of all... Hopefully GCC will fix this in the near future. – mark Dec 21 '11 at 18:44
  • Hmm you can probably get around spotty compiler/library support by using my answer here http://stackoverflow.com/a/8446901/85371 – sehe Dec 21 '11 at 22:02
3

How about a solution that uses unique_ptr:

#include <iostream>
#include <fstream>
#include <memory>

using namespace std;

class foo {
    unique_ptr<ifstream> input;

public:
    foo( unique_ptr<std::ifstream>&& in ): input( std::move(in) ) { }
};

int main()
{
    std::unique_ptr<ifstream> myStream( new ifstream( "something" ) );
    foo f( std::move( myStream ) );
    return 0;
}

This could be improved by using a hand rolled make_unique template (see: suttersmill gotw #102

mark
  • 7,381
  • 5
  • 36
  • 61
2

I think the important line in the question is this:

foo var1(std::ifstream("/tmp/something"));

The questioner just wants this, or something like it, to 'just work', and the questioner doesn't actually require moves or anything like that. (Is this correct?)

Here's an answer:

#include <fstream>
#include <iostream>
using namespace std;
template <class T>
class foo {
public:
        typedef typename std :: remove_reference<T> :: type value_type;
        value_type input;
        template <class ... Args>
        foo(Args... args): input(args...) {}
};

int main() {
        foo<std::ifstream> var1("/tmp/something");
}

Do you require a version that only accepts types which can be converted to istream ? You could include another members of type istream& and initialize this with input. This would force the template to only use suitable types.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
1

I think you have to forget about move and &&. You can't easily construct an istream - it's a very basic type which doesn't have many suitable constructors. The error is telling you that istream can't be moved.

You might have to explicitly control the lifetime yourself, and have foo keep a reference.

class foo {
public:
    std::istream &input;

    foo(std::istream& in): input(in) { }
};

std::ifstream a_suitable_variable ("/tmp/something");
foo(a_suitable_variable);

Another option is to just pass in the filename to the constructor.

class foo {
public:
    std::ifstream input; // NOT a reference this time

    foo(string s): input(s) { }
};
foo("/tmp/something");
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88