argcで出力ストリームを切り替え

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
foobar2015

argcで出力ストリームを切り替え

#1

投稿記事 by foobar2015 » 5年前

コマンドライン引数に入力ファイル名が与えられたかによって、入力ストリームをcinとifstreamで切り替えたいと思っています。
そこで、次のように書くとcinのコピーが起こってしまうため(?)コンパイルエラーとなり、

コード:

// Error
int main(int argc, char * argv[]){
  ifstream & is = argc==2 ? ifstream(argv[1], ios::in) : cin;
}
しかし次のように書くとコンパイルが通ります。

コード:

// OK
int doit(istream & in){
    int x; in >> x;
    cout << x << endl;
    return x;
}
int main(int argc, char * argv[]){
    if(argc==2){
        ifstream ifs(argv[1], ios::in);
        doit(ifs);
    } else {
        doit(cin);
    }
}

これは何故ですか?
また、できれば始めに挙げた方法で書きたいのですが、うまく行く方法はありますか?(良くない書き方だったりするのでしょうか)

アバター
h2so5
副管理人
記事: 2212
登録日時: 10年前
住所: 東京
連絡を取る:

Re: argcで出力ストリームを切り替え

#2

投稿記事 by h2so5 » 5年前

std::cinはifstreamを継承していないため、ifstream&をstd::cinで初期化することはできません。
また、ifstream(argv[1], ios::in)は右辺値なので左辺値参照を初期化することもできません。

参照を利用したいのであれば以下のようになると思います。

コード:

int main(int argc, char * argv[]){
	ifstream ifs;
	istream* isp = &cin;
	if (argc == 2) {
		ifs.open(argv[1], ios::in);
		if (ifs) isp = &ifs;
	}
	istream &is = *isp;
}

アバター
nullptr
記事: 239
登録日時: 8年前

Re: argcで出力ストリームを切り替え

#3

投稿記事 by nullptr » 5年前

難しい内容ですがh2so5さんに少し補足をします。
non-const lvalue referenceが束縛できるのはlvalueのみです。故に初期化式はlvalueでなければなりません。
ただし、const lvalue referenceの場合は、初期化式の束縛したオブジェクトの寿命が延長されます。const lvalue referenceはlvalueもprvalueもxvalueも束縛できます。

コード:

std::ifstream& s = std::move(std::ifstream("", std::ios::in)); // error
const std::ifstream& s = std::move(std::ifstream("", std::ios::in)); // ok
最後に編集したユーザー nullptr on 2015年1月11日(日) 08:23 [ 編集 1 回目 ]
 
 
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
?
Is the は :
order C++? ✜
     糸冬   
  ――――――――
  制作・著作 NHK
 
 

アバター
nullptr
記事: 239
登録日時: 8年前

Re: argcで出力ストリームを切り替え

#4

投稿記事 by nullptr » 5年前

doitの引数はistreamの参照になっているのでistreamであるcinが正しく渡せてコンパイルできます。
参照の初期化式の三項演算子の後者二項のvalue categoryを一致させる必要があるようです。規格の該当部分がちょっとわからないので正しい引用ができないのですが、直感的で妥当な仕様であると思います。
故にisはifstreamではなくistreamの参照にし、ifstreamをlvalueにすることでコンパイルできるはずです。

コード:

ifstream f(argv[1], ios::in);
istream& is = ( argc == 2 ) ? f : cin;
ifstreamをrvalueにするなら、cinの方もrvalueにしなくてはならず、以下のようになります。

コード:

const istream& is = ( argc == 2 ) ? ifstream(argv[1], ios::in) : std::move(cin);
istream&& is = ( argc == 2 ) ? ifstream(argv[1], ios::in) : std::move(cin);
const istream&& is = ( argc == 2 ) ? ifstream(argv[1], ios::in) : std::move(cin);
ただし、std::cinをムーブする時点で殆どの場合は不正な動作である可能性が高いので、おすすめしません。
最後に編集したユーザー nullptr on 2015年1月12日(月) 10:16 [ 編集 2 回目 ]
 
 
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
?
Is the は :
order C++? ✜
     糸冬   
  ――――――――
  制作・著作 NHK
 
 

アバター
h2so5
副管理人
記事: 2212
登録日時: 10年前
住所: 東京
連絡を取る:

Re: argcで出力ストリームを切り替え

#5

投稿記事 by h2so5 » 5年前

nullptrさん
そのコードだとargcをチェックする前にargv[1]にアクセスしてしまいます。

zeek

Re: argcで出力ストリームを切り替え

#6

投稿記事 by zeek » 5年前

コード:

    ifstream ifs;
    istream &is = argc == 2 ? ifs = ifstream(argv[1]) : cin;
とか

アバター
nullptr
記事: 239
登録日時: 8年前

Re: argcで出力ストリームを切り替え

#7

投稿記事 by nullptr » 5年前

h2so5 さんが書きました:nullptrさん
そのコードだとargcをチェックする前にargv[1]にアクセスしてしまいます。
OH
 
 
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
?
Is the は :
order C++? ✜
     糸冬   
  ――――――――
  制作・著作 NHK
 
 

foobar2015

Re: argcで出力ストリームを切り替え

#8

投稿記事 by foobar2015 » 5年前

ifstreamとcinの変換ができないことを見落としていました。無理に参照にする必要も無さそうですね…
皆様有難うございます。

かずま

Re: argcで出力ストリームを切り替え

#9

投稿記事 by かずま » 5年前

foobar2015 さんが書きました:ifstreamとcinの変換ができないことを見落としていました。無理に参照にする必要も無さそうですね…
無理なく参照にできますよ。

コード:

    ifstream ifs;
    istream & is = (argc == 2) ? (ifs.open(argv[1]), ifs) : cin;
(ifs.open(argv[1], ifs) の代わりに、(ifs = ifstream(argv[1])) だと、
ifstream の一時オブジェクトを生成し、それを代入演算子でコピーし、
一時オブジェクトのデストラクタが呼ばれるという無駄がありますが、
open() なら素直です。

閉鎖

“C言語何でも質問掲示板” へ戻る