7

I am trying to write my program so that it can process either StdIn or a file specified on the command line.

I'm doing this by trying to initialize a reference to an istream to either refer to cin or an ifstream, using a conditional.

(similar techniques are described here and here)

But when I try with ifstream, I seem to get an error that the basic_istream move-constructor is declared protected.

istream& refToCIN  ( cin );                      // This is OK
const istream& refToFile = ifstream(args[1]);    // This is OK

const istream& inStream ( FileIsProvided()? ifstream(args[1]) : cin );
// This causes error:
// std::basic_istream<char,std::char_traits<char>>::basic_istream' : 
// cannot access protected member declared in class std::basic_istream<char,std::char_traits<char>>

ProcessStream(inStream); // This could either be a file or cin

Can this be reasonably done this way? Is there a good alternative I'm overlooking?

Community
  • 1
  • 1
abelenky
  • 63,815
  • 23
  • 109
  • 159
  • `ifstream(args[1])` creates a temporary. You can't bind non-const reference to it. And your first example (reftofile) defines a function, not an object. – SergeyA Jun 01 '16 at 19:35
  • I added `const`, but it does not change the essential problem. – abelenky Jun 01 '16 at 19:37
  • Yes, I will explain in the answer. – SergeyA Jun 01 '16 at 19:39
  • 1
    Related, possible duplicate: https://stackoverflow.com/questions/24706480/why-does-stdistringstream-appear-to-resolve-differently-to-stdifstream-in-th – Galik Jun 01 '16 at 19:42
  • @abelenky Is `FileIsProvided()` usable as `constexpr` actually? – πάντα ῥεῖ Jun 01 '16 at 19:42
  • 4
    I wonder who downvoted / VTCed the question. There is nothing inherently wrong with it. – SergeyA Jun 01 '16 at 19:44
  • 1
    I suspect the VTC was by a "rage-voter". My questions are frequently down-voted by someone who I suspect has a grudge against me. – abelenky Jun 01 '16 at 20:00
  • @SergeyA Well, it was me. I'm not a _rage voter_ or alike. Besides I grip what's the problem asked for is, the question clearly misses to provide a [MCVE]. This could be easily improved. – πάντα ῥεῖ Jun 01 '16 at 20:09
  • 1
    That code is very nearly ready to compile as is. Everyone else understood it. Here's your [***MCVE***](http://ideone.com/CJ7vl5) – abelenky Jun 01 '16 at 20:15
  • 1
    @πάνταῥεῖ, I know you are not. I do not neccessarily agree with you here, but we don't have to agree on everything, do we? :) – SergeyA Jun 01 '16 at 20:21
  • @SergeyA _"do we? :)"_ Certainly not. I'm just failing at occasional times with my judgments though. – πάντα ῥεῖ Jun 01 '16 at 20:26

2 Answers2

3

The problem with your code is following:

Your left-hand side of the ternary operator is a temporary (rvalue). However, your right hand-side is an lvalue (cin is an lvalue). As a result, compiler is trying to create a temporary out of cin, and fails because of copy constructor being not available.

As for the sultions - you can simply replace rdbuf() of cin with rdbuf() of your file, and use cin everywhere.


Here's the ultimate solution OP came up with:

ifstream file;
std::streambuf* old_cin_buf = cin.rdbuf(); // Store the old value
if (FileIsProvided())
{
    file.open(args[1]);
    old_cin_buf = cin.rdbuf(file.rdbuf()); // Replace the ReadBuffer on cin.
    // Store the previous value as well.
}
// Use cin for all operations now.  It will either use the File or StdIn as appropriate.
...
// Restore the original value, in case it was changed by using a file.
cin.rdbuf(old_cin_buf); // This is better be done before file object here goes out of scope
abelenky
  • 63,815
  • 23
  • 109
  • 159
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • Forgive the edit, but I wanted to show my final solution based on your answer, without a separate post. – abelenky Jun 01 '16 at 20:18
  • 2
    @abelenky - edit forgiven :) One thing though - you better to replace the rdbuf back before closing the file. – SergeyA Jun 01 '16 at 20:19
  • Alright, now my answer is downvoted. Can the venerable downvoter please provide the reasoning? – SergeyA Jun 01 '16 at 20:34
1

This smells like an XY problem because you don't need a ternary conditional or reference here.

As a matter of convention, many programs use - to denote stdin rather than omitting a filename. That's one possible avenue. On a similar line of thought, I would use Boost.ProgramOptions or getopt instead of manually parsing the command line. This will indirectly solve your XY problem as it'll make the FileIsProvided() function redundant and you'll be getting your options via other methods than using argv[1] directly.

If you have C++11, there's smart pointers or std::reference_wrapper, which allows you to "reseat" references.

As a anti-motivator, consider that classes like ostream_joiner keep a pointer to their internal stream objects, not a reference. Besides, I doubt that you enjoy the thought of having to deal with dangling references from innocuous looking code.

Otherwise...

if (FileIsProvided())
{
    std::ifstream ifs(argv[1]);
    if (ifs)
    {
        ProcessStream(ifs);
    }
} else {
    ProcessStream(std::cin);
}
  • 1
    I don't intend to import the Boost libraries when the entire, finished program is only 70 lines long, and compiles to 20K. In a larger-scale program this may be advisable. But when the whole thing fits on screen at once, I don't mind managing a few items myself. – abelenky Jun 01 '16 at 20:31