C言語は関数の多値返却を標準機能としてサポートできるか

C言語における多値返却は,

  1. 構造体を返す
  2. 関数の引数に参照を渡してそこに値を入れる

などが考えられるけど,

int, double
f(void)
{
	return 1, 0.1;
}

int x, double y = f();

みたいなコードの書き方をC言語標準としてサポートできるかという議論を Twitter で行ったのでそのまとめ.

  • 構文解釈がうまくいくか
  • オーバーロードを解決できるか
  • ある関数の引数に多値返却関数が使われた場合の挙動をどうするか
  • 機械語にできるか
  • 標準として適切か

のような問題が挙った.

結論

  • のように < > で囲めば構文解釈は容易らしいが,C++でテンプレート構文とコンフリクトしないかな?
  • オーバーロードの解決は可能らしい,計算は増えるが実用上は問題ないだろう.
  • 引数に多値返却関数が使われた場合の挙動は適切に決める.とりあえずエラーでもいいのでは.
  • 機械語レベルでは構造体の返却と同じ扱いで実装可能
  • C言語標準機能として適切かはかなり疑問
  • UNIX の記述言語であることを考慮すると,コンパイル時に返り値の数が決定されている必要がある(動的にメモリ確保を行わない)
  • 標準じゃなくて良いなら Cyclone の Polymorphic Functions を使おう.
  • C++だったら pair とか tuple を使おう.

Twitter のログ


xharaken
昔から思ってることだが,C言語で一番気に入らない点は,関数の返り値が1個しか返せないこと.一般論として,「関数」とは複数の入力を受け付けて複数の出力を返すものなので,1個しか返り値を返せないのはあまりに不便. link

xharaken
このせいで,言語仕様に振り回されてコーディングが無駄に汚くなっているように感じることがよくある. link

yuyarin
C 言語が関数の返り値を1つまたは無し(void)しかサポートしない合理的な理由は何だろう. link

koizuka
@yuyarin いわゆるALGOL系の流れは大体「戻り値一つの関数」と「戻り値なしの手続き」だったんでは link

kodam
再帰ができないから? RT @yuyarin : C 言語が関数の返り値を1つまたは無し(void)しかサポートしない合理的な理由は何だろう. link

xharaken
@yuyarin わかんない.現状のC言語でも大きい構造体をそのまま返り値にできるわけなので,サイズ的な制限が問題になるわけじゃないと思うんだけど. link

yuyarin
ALGOL 系という歴史的事情があったとしても,拡張は考えられていてもおかしくないと思う.オーバーロードで何か問題が起きるのかなぁ. link

yuyarin
できないかなぁ?できない例を考えてみます. RT @kodam : 再帰ができないから? RT @yuyarin : C 言語が関数の返り値を1つまたは無し(void)しかサポートしない合理的な理由は何だろう. link

GreenKei
@yuyarin 確かに気になりますね。ひとつの関数にはひとつの役割しか持たせるべきでないとかそんな感じでしょうか? link

kodam
@yuyarin 複数あるというイメージがもてないけど、2つ以上あったら、指数関数的に計算量が増えそう link

yuyarin
入出力の記述方法の問題だと思います. RT @GreenKei : @yuyarin 確かに気になりますね。ひとつの関数にはひとつの役割しか持たせるべきでないとかそんな感じでしょうか? link

yuyarin
確かにオーバーロードの解決に費やす計算は増えると思いますが...実用上はそれほどクリティカルではない気がします. RT @kodam : @yuyarin 複数あるというイメージがもてないけど、2つ以上あったら、指数関数的に計算量が増えそう link

kodam
@yuyarin なるほど。逆に複数返り値を持つ関数を扱える言語ってあるんですか? link

yuyarin
perl, python, ruby はできたと思います.スクリプト言語ですが. RT @kodam : @yuyarin なるほど。逆に複数返り値を持つ関数を扱える言語ってあるんですか? link

yuyarin
複数返り値の実体は perl, ruby は配列,python はタプルだったっけ. link

mutaguchi
PowerShellも配列かなぁ 結局ArrayもTupleもオブジェクトじゃないかという気がしたり RT @yuyarin : 複数返り値の実体は perl, ruby は配列,python はタプルだったっけ. link

yuyarin
エラーコードを取得するのに引数の最後に boost::system::error_code & ec とか埋め込むのは気持ち悪いですよね. link

yuyarin
呼び出し側で x, y = func(); みたいな記述ができないといけないな. link

yuyarin
同じような議論がどこかで絶対行われているので探したいなぁ. link

flyingwktk
matlabとかだと帰り値の数は自由なんだけどねぇ。 RT: @yuyarin : 呼び出し側で x, y = func(); みたいな記述ができないといけないな. link

yuyarin
Q「複数の値を返すにはどうしたらいいですか?」A「構造体を返すとか引数で参照渡しとかをしてください」みたいなのばっかりひっかかるから難しい. link

hideaki_t
@yuyarin 多値が返せると便利な場面って多いですよね。名前つけるの面倒くさいので link

wraith13
boost::tuple まわりで近いことできなかったっけ? RT @yuyarin : 呼び出し側で x, y = func(); みたいな記述ができないといけないな. [電波注意] link

wraith13
@yuyarin だ、そうです。 RT @cpp_akira : . @wraith13 tie [電波注意] link

yuyarin
tie(x, y, z) = make_tuple(1,0.1, '1'); みたいに書けますね. RT @wraith13 : boost::tuple まわりで近いことできなかったっけ? RT @yuyarin : 呼び出し側で x, y = func(); みたいな記述 link

hrjn
funcA(funvcB(args))みたいのが出来ないからじゃない?引数のサポートの仕方も変えたりと意外と難しい要素がたくさんありそう。 RT @yuyarin : C 言語が関数の返り値を1つまたは無し(void)しかサポートしない合理的な理由は何だろう. link

yuyarin
どちらかというと funcA(1, funcB(), '1'); で funcB が複数返り値みたいな場合が問題になりそう. RT @hrjn : funcA(funvcB(args))みたいのが出来ないからじゃない?引数のサポートの仕方も変えたりと意外と難しい要素がたくさ link

hrjn
.@yuyarin そだね。rubyだと配列を引数に空気を読んで展開してくれたりして、結構想定しない動作する事ある。 link

yuyarin
はい,その通りですが,構造体を定義すること無く多値を返す記述方法を言語標準でサポートできないか考えています. RT @fujisho : @yuyarin 構造体を返せるので多値を返せると言ってもいいのでは > C言語 link

yuyarin
結論: boost::tuple 使おうぜ link

hrjn
.@yuyarin 凄く冷静に考えれば、マシンコード的に二つの別々の場所のレジスタをさすのは難しいんじゃないかという事に気づいた。もしくは、あまり効率がよく無さそう。 link

yuyarin
空気を読む必要があったり曖昧だったりした場合は全部コンパイルエラーで弾いてしまえばいい気がするけど,それによって不便になる場合を考えないとなぁ. RT @hrjn : .@yuyarin そだね。rubyだと配列を引数に空気を読んで展開してくれたりして、結構想定しない動作す link

yuyarin
「さす」の主語がわからないのでなんとも言えませんが,レジスタ返しもスタック返しもできると思います. RT @hrjn : .@yuyarin 凄く冷静に考えれば、マシンコード的に二つの別々の場所のレジスタをさすのは難しいんじゃないかという事に気づいた。もしくは、あまり効率が link

hrjn
.@yuyarin もしくは、結局マシンコードレベルだと、構造体みたいなものを定義して値を返す構造になるだけなので、プログラミング言語作る様な低レイヤーのスーパーハカーはそれで何のメリットがあるのかわからないから実装しない。とかありそう。 link

yuyarin
マシンコードレベルなのに構造体みたいなものを定義して,というのはちょっとわかりません... RT @hrjn : .@yuyarin もしくは、結局マシンコードレベルだと、構造体みたいなものを定義して値を返す構造になるだけなので、プログラミング言語作る様な低レイヤーのスーパ link

yuyarin
そういえば double 型のマシンコードでの扱い方を知らない...勉強してくる. link

hrjn
.@yuyarin コンパイラが何をどうしてるのかよく知らないけど、単純に32bitにおさまる返り値を返すか、構造体なり配列なりtreeなりの先頭のポインタを返す方がシンプルで効率的だと思わない?ということ。 link

hrjn
.@yuyarin 例えば2つの返り値を返そうとすると、少なくとも2つの値を格納しているメモリか値へのポインタを持っている配列(連続した領域)を返すか、2つのメモリか値へのそれぞれのポインタを持っている領域へのポインタ(構造体)が必要なのでということ。日本語がなんかおかしい。 link

yuyarin
うーん...その考え方が必要になる箇所がよくわかりません... RT @hrjn : .@yuyarin 例えば2つの返り値を返そうとすると、少なくとも2つの値を格納しているメモリか値へのポインタを持っている配列(連続した領域)を返すか、2つのメモリか値へのそれぞれのポイン link

hrjn
.@yuyarin どうやって値返却させるかにもよるとは思うけど。少なくとも今言った方法は確実にあたい返却できるけど、素人考えなので効率的じゃないかもしれないし。どういうふうに関数の値返却ってするの? link

yuyarin
ほとんどの呼び出し規約では eax レジスタに入れておくだけです.構造体で返す場合は中身がどこかにコピーされてアドレスが eax で返ってきます. RT @hrjn : .@yuyarin どうやって値返却させるかにもよるとは思うけど。少なくとも今言った方法は確実にあたい返 link

yuyarin
edx とか ecx で値を返す場合の不都合ってあるのだろうか. link

yuyarin
基本は構造体と同じようにスタックに積んで返せばいいと思うけど,__fastcall みたくレジスタで返してもいいよね,不都合が無ければ. link

hrjn
.@yuyarin うーん。eaxとかはよく知らないけど、別にレジスタである事には変わりないしな・・・・単純にそのeaxレジスタから変数が確保した領域にコピーをする時とかにちょっと面倒な気がしたんだけど。なんかそうでもない様な気もしてきた。 link

hrjn
これ以上はレジスタと関数と変数関連の値の移動の仕方とか、返り値が入っている場所をさすポインタをどう持ってるのかとか真面目に調べないとわからなそう。 link

hrjn
ただ返り値が一つである事で、効率化させる事は出来ると思うんだよね。例えば返り値用のスタックをあらかじめある程度とっておくとかは明らかに返り値が一つの方が効率的に実装できる。返り値がn個になると、別途に返り値の数を管理する構造が必要だったり結局構造体返すのと同じにする等しな link

yuyarin
構造体を返す場合でもその処理は変わらないですよね. RT @hrjn : .@yuyarin うーん。eaxとかはよく知らないけど、別にレジスタである事には変わりないしな・・・・単純にそのeaxレジスタから変数が確保した領域にコピーをする時とかにちょっと面倒な気がしたんだけ link

yuyarin
コンパイル時に比較的複雑な処理になるため非効率と言うことは出来るけど,実行時に性能が構造体の返り値以上に落ちることは無いと思う. RT @hrjn : ただ返り値が一つである事で、効率化させる事は出来ると思うんだよね。例えば返り値用のスタックをあらかじめある程度とっておくと link

hrjn
.@yuyarin えぇっと、やっと通じた。TL的には2回目の同じ発言だけど、結局多値返却したところで、マシンコードになおる頃には同じになっている気がするし、プログラムの書き方によってはコンパイラがうまく最適化できなかったりして、あまりメリットがないんじゃないのと言いたかった。 link

hrjn
.@yuyarin 結局その「コンパイル時に比較的複雑な処理になる」という部分で、ちゃんと何とかなるのかなというところの疑問が言いたかった。俺のボキャぶらりの貧弱さが半端ない。 link

hrjn
@shinji_kono 素人質問でで申し訳無いのですが、そういうのってどこで規定されてるんですか?? link

shinji_kono
@hrjn 返り値の問題は、複数の値をどこに書けば良いかだけ。入力の場合は細かく規定されている。出力も細かく規定すれば良いだけ。一つにする利点はなに一つない。 link

hrjn
結局こういうことっぽいのかな? RT @shinji_kono : @hrjn 返り値の問題は、複数の値をどこに書けば良いかだけ。入力の場合は細かく規定されている。出力も細かく規定すれば良いだけ。一つにする利点はなに一つない。 link

hrjn
というよりも、そもそもコンパイラの問題なので「比較的複雑な処理になる」という部分が肝心なんだと俺は思っていたという話。 link

yuyarin
元は構造体をわざわざ定義せずに多値を返したいということなので,マシンコードもその最適化も構造体と同じで良いのです. RT @hrjn : .@yuyarin えぇっと、やっと通じた。TL的には2回目の同じ発言だけど、結局多値返却したところで、マシンコードになおる頃には同じに link

yuyarin
@hrjn やっと話が通じましたねw link

yuyarin
僕の最初のポストはまさにこの疑問なのですよ. RT @hrjn : .@yuyarin 結局その「コンパイル時に比較的複雑な処理になる」という部分で、ちゃんと何とかなるのかなというところの疑問が言いたかった。俺のボキャぶらりの貧弱さが半端ない。 link

Cryolite
これ,発端は @yuyarin さんか. http://twitter.com/yuyarin/... link

yuyarin
. @Cryolite さらにその発端はこちらです http://bit.ly/Twl3Z link

yuyarin
結局コンパイラとか言語処理がわかってないので結論はでないか...でもなんとかなりそうなきもする. link

pi8027
@yuyarin ドラゴンブックとか読みましょう。 link

koizuka
@yuyarin C言語に関しては「高級アセンブラ」として、最低限の組み合わせで何でもできる、という目的には、さくっと「コンパイラが書けて」、ちゃんとうごく、というのは重要だったんじゃないかなー link

koizuka
@yuyarin あと「カーネルなどのシステムプログラミング」目的には、動的メモリ確保関数は使えないので、言語ではなくライブラリ扱い。従って動的メモリ管理が必要な要素は言語には組み入れない、というのもありそう link

yuyarin
なるほど,そういう点では標準として採用するのはハードルが高そうですね. RT @koizuka : @yuyarin C言語に関しては「高級アセンブラ」として、最低限の組み合わせで何でもできる、という目的には、さくっと「コンパイラが書けて」、ちゃんとうごく、というのは重要だ link

yuyarin
多値返却を実装するなら,僕の考えでは構造体と同じ扱い(スタック返し)をすればいいので,動的メモリ確保(malloc等のことですか?)は必要ないと思います. RT @koizuka : @yuyarin あと「カーネルなどのシステムプログラミング」目的には、動的メモリ確保関数 link

koizuka
@showyou うん、だからそのmallocを排除する意味で言ってたんだけど、「言語」じゃ範囲が広すぎたな。「構文」と「ライブラリ」を合わせて「言語」というなら、「構文」の構成要件にしない、という感じ link

koizuka
@yuyarin 多値返却、といったときに、「動的に個数が決定される任意個の返却」を想定してしまっていたが、単純にコンパイル時決定なら問題はないか。 link

koizuka
@yuyarin あとは「式」のデザインの問題になるか。決め事だから趣味の問題もありそうだよなあ。「実用言語」として作ってたようだし link

koizuka
@yuyarin 「実用言語」ってまた妙な言葉を作ってしまった。つまりUNIXを動かすために作った言語、ということで、それさえ達成できればよかったんだろうし link

yuyarin
あ,はい.とりあえずはコンパイル時に返す値の個数が決定できるものを想定しています. RT @koizuka : @yuyarin 多値返却、といったときに、「動的に個数が決定される任意個の返却」を想定してしまっていたが、単純にコンパイル時決定なら問題はないか。 link

yuyarin
PHP の preg_match みたいな動的に返り値(出力)の個数が決まる関数は難しいなぁ. link

koizuka
C言語に多値返却が「当初無い」理由と、「今もなお無い」理由はまた違うか。"void関数"なんか後から付いたんだから、多値を入れる余地もまたあったはずだな link

koizuka
多値の扱いは「構造体で実現」とみなしたのかな。 link

koizuka
構造体の実体コピーなんて「一見して重そう」だからC言語書きにはキモイよねw link

koizuka
実際にはコンパイラが細工して隠し引数ポインタ経由したりしてコピーを減らしいても、それが予想しにくいしな link

koizuka
C++の話じゃなくてCの話ね。 link

yuyarin
RT @ranha : 標準のCを変えたいって言うんじゃなければ勝手に変えれば良いだろうし、勝手に変わってるのがさいくろんでそ http://bit.ly/g6dfT link

koizuka
@t_yano inline関数だとC++か。大分まえからそんな感じな気がw link

koizuka
最近ひっぱりだしてないので規格書が手元にないな > C, C++ link

koizuka
PascalBorland製品が快適でよく使っていたな。言語拡張しまくりだったけど。 link

_udonchan
プログラミング言語って抽象レイヤであるべきであって,コンパイラが云々,バイトコードが云々ってのを利用者が意識するのは,アセンブラかもしくはイージーアセンブラであってプログラミング言語としてはどうかと思う.例えばC言語の関数は数学の関数のモデルである訳でバイトコードがどう link

_udonchan
まぁでもその辺は宗教論争なような気がする.インターンで出会ったスーパーハカーな人は,機械語に近いという理由でCが好きだと言っていたし. link

_udonchan
正直なところ,Cはカーネル組むための言語だったという側面を考えると,只の抽象レイヤとして振る舞うだけではだめだよなあとは思うのだけど. link

suztomo
なんだこのゆうやりんによるCの関数の返り値が複数になりうるかというタイムライン。 link

yuyarin
C のどこが低次元だ! RT @suu_g : みんなC大好きだな、この低次元大好きなロリコンどもめ!! link

suztomo
あとでyuyarinがまとめ書いてくれるだろうからそれを読もう。あとranhaさんの張ってたサイクロンは「あとでよむ」 link

t_yano
あ、inlineってそういやC++だったか。 *P3 link

ranha
おおよそCだからで片付いてぱいちょんとかは型無いからむしろなんでも出来ないとおかしいし、型があって出来る言語は型があるなりにCと期限が違うしCでやりたいんだったらプリプロセサさんとかだと思うし、いちいちやるの嫌だなーだったらCycloneで良いんじゃ無いですかねみたいな link

DecimalBloat
Boost.LambdaやBoost.Bindを多用して異常に長くなったコンパイル時間をROに充てる link

DecimalBloat
tuple unique_name = ラムダ式か何かで初期化; int x = get<1>(unique_name); int y = get<2>(unique_name); link

ranha
標準のCを変えたいって言うんじゃなければ勝手に変えれば良いだろうし、勝手に変わってるのがさいくろんでそ http://bit.ly/g6dfT link

DecimalBloat
というような感じに展開されるようにプリプロセッサでごにょる link

DecimalBloat
ただの構文遊びだから全然役には立たない!それこそがfan! link

yugui
inline関数から小さな構造体を返した際には綺麗に最適化されて全部レジスタ直とかになる素敵な時代ですよ link

t_yano
時代は進んでるなあ RT @yugui : inline関数から小さな構造体を返した際には綺麗に最適化されて全部レジスタ直とかになる素敵な時代ですよ *P3 link

t_yano
PascalでRecord渡すと参照渡しだったよなたしか。クラシックMacPascalAPIだったのでやったけど途中でCに移行したのでだいぶ忘れた。 *P3 link

yuyarin
おお,すてき! RT @yugui : inline関数から小さな構造体を返した際には綺麗に最適化されて全部レジスタ直とかになる素敵な時代ですよ link

suu_g
@yuyarin えっ? link

suu_g
@yuyarin えっ link

DecimalBloat
Cというのはつまり12だし、12とかまぁロリコンでしょう link

ranha
.@DecimalBloat 亜美真美はC!!!!!!!!!!!!!!!!????? link

Cryolite
この感じ……そろそろ C++ は12なのか13なのかについての篤い議論が始まる流れっ!!! link

ranha
すると高槻やよいさんはD link

yuzuhara
構造体に共用体が入ってて不気味ってのはわからないなぁ... RT @nojiri_h : 構造体の中に共用体なんて不気味なものもあるんだよな、C言語って。 link

yuzuhara
Cとか高級言語だし link

Cryolite
この感じ……そろそろ C++ は12なのか13なのかについての篤い議論が始まる流れっ!!! link

DecimalBloat
12なんだけど、第2期が始まると13になっているとかそういうあれ link

ranha
Cは亜美か真美で、C++亜美真美です link

yuyarin
はい. RT @DecimalBloat : Cというのはつまり12だし、12とかまぁロリコンでしょう link

tokoroten
@yuyarin 業務が逆アセの俺に謝れwwwww  酔っ払いなう link

tgbt
@yuyarin スクリプターは年増好き? *Tw* link

pi8027
@yuyarin <>で括るとパーサー的には一番楽な気がする。普通にコンマ区切りだとコンマ演算子との区別がむずい。 link

koizuka
亜美真美やよい・・・!? Reading C言語の関数の多値返却は標準機能としてサポートできるか - yuyarinの日記 http://d.hatena.ne.jp/yuyarin/20090825/1251135887 link

pi8027
むずいってかむりか。そういうのを特別扱いするとトリッキーな事しようとして爆死する人が出ますね。 link

iratqq
多値ってcall/ccのことでしょ。 link

pi8027
あと多値って変数に束縛しないと使えないのがなぁ。 link

pi8027
@yuyarin 良く考えてみたらか{}でも大丈夫でした。 link

shamoshamo
@yuyarin して結論みたいなのはあるのかしら link

Omegamega
D言語使い「うっうー」 link

pi8027
@yuyarin なぜ大丈夫かという話:{}は式の外でしか使われていない。は配列アクセスの演算子なのでarray[index]の形でしか使われていないので、複数の式をコンマ区切りにして括るだけなら全く違う形式としてパースさせられる。 link

yuyarin
僕の結論は「機械語には落とせるので構文解析屋さん頑張ってください」です.あと引数に多値返却関数が使われた場合の挙動定義が必要でしょうか. RT @shamoshamo : @yuyarin して結論みたいなのはあるのかしら link

suztomo
@yuyarin おつかれさまです。 link

shamoshamo
@yuyarin だよね。実装は十分可能だよね。いろんな人がいろんな話題をふりまいててなかなかおもしろかった。ログ作成乙。 link

koizuka
C言語が12でD言語が13だとすると、C++言語は「右辺値をインクリメント」となるのでエラーです。 link

shachi
わろたw RT @koizuka : C言語が12でD言語が13だとすると、C++言語は「右辺値をインクリメント」となるのでエラーです。 link

yuyarin
[] と {} とだったら <> がよいねー RT @pi8027 : @yuyarin <>で括るとパーサー的には一番楽な気がする。普通にコンマ区切りだとコンマ演算子との区別がむずい。 link

pi8027
@yuyarin = f(arg) みたいな形で多値の代入ができるというのと、var = f(arg) みたいな形式の場合に1番目を取るというような感じが良さそう。var = f(arg) [1] みたいなので2番目になるとさらに親切設計な感じがするなぁ。 link