にゃははー

はへらー

returnとmoveと() ~ GCCを添えて

Boost.Move 1.56 で追加されたBOOST_MOVE_RET*1*2というマクロがあるのですが、GCC 4.9のC++14モードだけなぜかコンパイルできないと言う事象にぶつかってしまい、そういえばここらへんの暗黙のmoveとかって最後にドタバタしてたなぁと思いつつあんまりしっかり見ていなかったのですが、これを気に調べたのでまとめます。

基本的にコードを書いたりする上では気にする必要のない部分です。

BOOST_MOVE_RETマクロ自体はそれほど使い方が難しいわけではないですが、一応例示しておきます。

#include <boost/move/core.hpp>
struct movable
{
    BOOST_COPYABLE_AND_MOVABLE(movable) // copyもmoveもできる
public:

    movable() { }
    movable(BOOST_RV_REF(movable)) { } // move ctor
    movable(const movable &) { }       // copy ctor
};

movable foo()
{
    movable m;
    return BOOST_MOVE_RET(movable, m);
}

上記コードは、C++03ではヘルパーを介しつつBoost.Moveを使ってmove ctorを呼びます。

一方、C++11以降では単に

movable foo()
{
    movable m;
    return (m);
}

へと置き換えられます。ところで、parenthesisで囲まれた式の型はdecltypeで調べれば解りますが、以下のようになります。

int i;
decltype(i)   // int
decltype((i)) // int &

これは(おそらく)割と知られていると思いますが、これをふまえて、parenthesisで囲まれている先のreturn文はmove ctorが呼ばれるでしょうか。それともcopy ctorが呼ばれるでしょうか。

もしあなたが、答えに窮し以下の様な式でmoveとcopyどちらが行われるか疑問に思った場合、いい線をいってます。

movable foo()
{
    movable m;
    return m;
}

return文に渡された`m`という式の型は`movable &`ではなく`movable`ですが、これは確かにlvalueです。lvalueを渡しているのにmove ctorが呼ばれるでしょうか。

結論から言うとparenthesisで囲っていようがいまいがmove ctorが呼ばれます。そして、意外にもmove ctorが呼ばれる理由にはNRVOが関係してきます。


このreturn文での挙動はn3376 12.8 [class.copy] paragraph 31と32に記載があります。
paragraph 31ではcopy/move ctorを"実際"に呼ばなくていい条件(つまり、どういう時にNRVOを行ってよいか)を列挙していて、paragraph 32では、先の条件を満たした場合にはその式をどう評価するかと言うことが書かれています。

paragraph 31によると、return文では

  • non-volatile automatic object
  • オブジェクトの型とreturn typeが同じcv-unqualified type

の場合、copy/move ctorを省略出来ると書いてあります。

そしてparagraph 32では、paragraph 31の条件を満たしたオブジェクトがlvalueで渡されたとき、copyのために呼ばれるctorは、

  • オブジェクトをrvalueとして扱った上でoverload resolutionする
  • 先のoverload resolutionが失敗、若しくはctorの第一引数がrv-refでなかった場合は、オブジェクトをlvalueとして扱い再度overload resolutionする
  • copy/move ctor呼び出しを"実際に"行わなくてもこの二段階のoverload resolutionは必ず実行される

とあります。

ちなみにこの文言だけではparenthesisで囲まれた場合moveできないと解釈できてしまうのですが、CWG DR1579でこの問題が解消されています。


ということで、return文でmove出来る謎が解決したわけですが、冒頭にも述べた

  • GCC 4.9.0, 4.9.1 (今のところGCC 4.9.2/5も)
  • C++14 mode

の場合、parenthesisで囲まれたオブジェクトを正しくrvalueとして扱わずにdelete指定されたcopy ctorを呼ぼうとしてしまうregressionがあります。

Bug 63437 – [4.9/5 regression][C++14] Parenthesized "movable but not copyable" object doesn't compile in return statement

C++11 modeではコンパイル出来るので今のところあまり大きい問題では無いと思いますが、1.56.0のBOOST_MOVE_RETをC++14 modeのGCC 4.9で使用した場合はこの問題にぶつかってしまいます。
とてもひどい方法ですが、この問題にぶつかってしまった場合には

#define BOOST_MOVE_MSVC_AUTO_MOVE_RETURN_BUG
#include <boost/move/core.hpp>

とすると回避できます。

GCCの修正かBoost 1.57のcloseのどちらか早いタイミングに合わせて修正を投げるつもりでいるので、1.57では改善されるはずです。

Boost.勉強会 #16で発表してきた

Boost.勉強会 #16 大阪 - boostjp

便利!電動歯ブラシ | Boost.勉強会 #16 大阪


この発表したあとにBoostの開発のこと聞かれたり色々したので、やっぱりキチガイキワモノ達の発表もいいけど、こういう簡単なセッションもあったほうがいいんだろうなぁと思った。数回前まではアキラさん(id:faith_and_brave)が一周の旅とかやってたけど、ああいうの。

それはそうとして、30分あれば十分やろとか思って、じゃぁ30分でって言ったら、実は60分喋ってたらしいし、そういうところはもうちょっとしっかりしたほうが良かったかなぁと思った。

それはそうとして、タイトル、あーメール送らんとなぁと思いながら何話すかその時は具体的に決めてなかったので、適当に適当を重ねて送った結果、割と混乱というか変な期待をさせていた感じあったので、それも考えたほうが良かったなぁと思った。

まぁ色々反省はあるけど、アリスSOSは見れたのでトータルとしてOKだった。

range-based forとstd名前空間

Boost.Coroutineに関してBoost users MLでrange-based forに関する面白い話が流れたので紹介したい。
この話は私も初めて聞いて驚いた。

C++11で新規追加されたrange-based forはconceptが削除されたタイミングでその挙動をどうするかで議論していた。なつかしい。
本の虫: range-based forに対する意見求む

議論の末(?)*1C++11では

  1. 配列を特別扱い
  2. class typeの場合、begin()/end()を呼べれば呼ぶ
  3. そうでなければstd名前空間をassociated namespacesに加えた上でADL

となりました。

しかし、ADLは挙動が非常にわかりづらく、特に問題なのがunqualified lookupするのかしないのかはADLの定義には書かれていません。
どちらかと言えば、unqualified lookupした結果、更にADLを加えるというのが本来意図された挙動でした。

さて、range-based forは最終的にADLするということになったわけですが、ここでunqualified lookupするかしないかという問題が出てきました。
結局この問題はDR1442*2となり、ADL時にはunqualified lookupをしないとなりました。
実際、GCCやClangといったコンパイラは、range-based for実装当初からunqualified lookupを行いません。

struct X { };

int main()
{
    struct X x;

    int * begin(X &);
    int * end(X &);

    begin(x); // unqualified lookup
    for (auto _ : x) ; // エラー: begin/endが見つからない
}

ところでこのDR1442ですが、よく見るとADLの挙動の明確化(unqualified lookupしないことの明確化)だけかと思うと、そうではありませんでした。
なんと、std名前空間をassociated namespaceに加えるという文言を削除しています。

つまり、これまでwell-formed*3だった以下のコードがill-formedとなるわけです。

namespace mylib {
    struct my_very_very_awesome_container { };
}

namespace std {
    int * begin(mylib::my_very_very_awesome_container &) { return nullptr; }
    int * end(mylib::my_very_very_awesome_container &) { return nullptr; }
}

int main()
{
    mylib::my_very_very_awesome_container c;

    for (auto x : c) (void)x; // std名前空間はassociated namespaceなのでbegin/endが呼ばれるはず
}

なにせstd名前空間をlookup対象にするという文言を消しているので。

このassociated namespaceに関する挙動については、melpon氏謹製のwandboxによると

  • clang 3.0 ~ 3.1
  • gcc 4.6 ~ 4.8

の間は、よく知られた(と思う)std名前空間をlookupに含める挙動を示すようだ。

C++11として発行された規格ではstd名前空間を含めるはずだが、C++14ではなく、DRとして取り込まれたのでこれはもはやC++11の挙動だといって過言ではないだろう*4
この挙動で頭を悩ませるのはboostのようなライブラリを書いている場合だろうから多分一般的には問題とはならないだろう。

Boost.Coroutineはstd名前空間にbegin/endを置いたので今回その対象となってしまった。


元議論:
Boost - Users - Cannot use Boost.Croutines with range based for

DR1442:
C++ Standard Core Language Defect Reports and Accepted Issues

*1:私はWGの人間ではないので積極的に議論されたかは知らない

*2:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442

*3:これがwell-definedだと言い切る自信がない。言い切る自信がある方、ぜひコメント頂きたい

*4:保証はしない

Debian GNU/Hurd 2013のインストールでパーティションを切りたい

Debianのインストールから、カーネルビルドまで...はいかない - にゃははー のハマリポイント4をなんとか回避できた。

結論から言うとprocfsがmountsを提供しないのが原因で、手動で切ったパーティションがマウントされているかを確認できていなかったのが原因でした。
具体的にはDebian GNU/Hurd 2013のインストールメディアは僅差でGNU Mach 1.3.99/Hurd 0.3という古いバージョンで収録されているが、手元のGNU Mach 1.4/Hurd 0.5では/proc/mountsがあったので、頑張って手動でこのファイルを作ればよかった。

手順としては、パーティション切ったところで何とかしてshellをとって

# settrans -g /proc
# cat > /proc/mounts
/dev/hd0s8 /target ext2fs defaults 0 0
/dev/hd0s3 /target/boot ext2fs defaults 0 0

ってする。

catめんどかったらnanoで。vimもviも無いです。
あとdpkgもないので、hurd 0.5を頑張って持ってきても手で頑張ってやらないといけないので普通にproc書いたほうが楽だと思う。

/proc/mounts 以外のprocfsなものはどうやら使わないみたいなので、/procにmountsしかなくても良かったです。

expert modeでしか確認してないので他のインストール方法でどうやってshell取るかは知らないです。多分出来ないです。
ほげ〜

Debianのインストールから、カーネルビルドまで...はいかない

ディストリビューション/パッケージマネージャー Advent Calendar 2013 - Qiita の18日目です。

今回はDebianのインストールとちょっとした操作説明したいと思います。Debian系は最近あんまり使ってないのですが、選択肢が現時点ではDebianしか無いので。

何を言っているかわからないと思いますが、すぐに分かります。

ほんとはカーネルのビルドまでやりたかったのですが、文章量が増えるだけなので諦めました。

続きを読む

Boost.Range でも regular が使いたい! - C++ AdC 2013

partake.in の12日目です。

もう一個のカレンダーの方で激おことかforkforkとか言ってたら、なんかみんな頑張ってるしカレンダー名に(fork)とか付いてしまってて申し訳ないなーとか思ってたり少しはしているのですが、特にカレンダー書く予定はないです。

ネタを Modularized Boost(GitHubへ移行したリポジトリ)を使用する - イグトランスの頭の中 に掻っ攫われてしまったので急遽頑張って考えました。
嘘です特に考えてませんでした。

ところがつい先日会社の同期が Boost.Range に足下掬われていて丁度いいかなと思ってネタにします。

続きを読む

C++ Advent Clandar 2013 1日目 (forkした方)

※注: これはforkした方の。C++ (fork) Advent Calendar 2013 - Adventar

今年もAdvent Calendarの季節です。
しかも驚くべきことにC++ Advent Calendarはforkしました。あと先に断っておきますが、25日やらないです。
立ちあげた本人が表にもこちらにも書こうとしていないのは一体どういうことでしょうか。激おこです。


表のカレンダーは多分ガチなので、こっちはこれまでのC++関連のAdvent Calendarを振り返って見たいと思います。
私が参加したカレンダーしか知らないので、もしかしたら抜けがあると思いますが、ご容赦を。

続きを読む