How to disappear completely RSSフィード

2012-02-13

Boost.勉強会 #8 大阪に参加してきた。

2/11日開催のBoost.勉強会 #8 大阪に参加して、Boost.Testについて発表してきました。

12月に募集が開始されたBoost.勉強会の参加登録が一瞬で埋まったので補欠参加枠で登録したにも関わらずフライングで飛行機を予約してしまったので、ちゃんと勉強会に参加できるかどうか分からなかったですが、無事に参加、そして発表もできてよかったです。


大阪行きということでそれだけ忙しそうなので、発表は当初するつもりはなかったですが、

某A社の方から発表を誘われたり、

知り合いのプログラマーからも「せっかく大阪まで行くんならついでに発表してきたらいいよー」なんていうアドバイスいただきましたので、なにかやることに。

2/1まで大学の定期試験があってあんまり時間なかったですが、普段からBoost.Testは使用していたのでなんとかなるだろうという予測のもと、まだBoost.勉強会でネタに上がってないであろうBoost.Testについて紹介する内容の発表をすることにしました。

(資料の準備は結果的に結構大変だったわけですが。。。)

(秋猫さんと内容がかぶる懸念もありましたが秋猫さんのほうはBoost.Test以外でやるという風に調整していただいたので助かりました。ありがとうございます!)


というわけでBoost.Testです。

http://www.ustream.tv/recorded/20366041

発表の内容はだいたいドキュメントの和訳と抜粋と使ってみた時の注意点的なものになってます。

出番ぎりぎりまで資料をいじっていたので、当日は資料に不備があったりしました。まだあるのであとで直したいと思います。


これまでは、Boost.Testを使っていてもあんまりちゃんとドキュメントを読んだことがなかったので、

今回の発表資料作りのためにあの長いドキュメントを読むのは苦労しました。

とはいえそれでも全部読んだわけではないですが。

特に、Test RunnerやTest Module Initializationあたりは、

ドキュメントも何書いてるかよく分からないしビルド設定もどうしたら良いか

よく分からないしちゃんと動くテストも書けなかったしで、発表内容からサクサクスキップすることにしました。

ただひとつ、ドキュメントのUnit Test Framework: User’s guideには、

"An advanced test runner may provide additional features, including interactive GUI interfaces, test coverage and profiling support."

という事が書いてありましたので、だれかGUIでProgress_Displayより綺麗な出力してくれるテストランナー作ったりしてないかなー。いたら面白いなーなんて思っています。

ところで、大変だったにも関わらず、ドキュメントの英語を読むのは結構楽しかったです。


発表自体についてですが、発表資料作りに追われて、トークの準備ができなかったので、ちょっとぐだぐだになってしまったことと、実行時のテスト設定で--run_testの使用例をスライドにするなり実際にデモを動かして見せるなどができなかったことが残念です。

でも、とりあえず無事に発表を終えることができてよかったです。


以下は発表以外の大阪旅行の記録。

続きを読む

2012-02-09

コンテナをストリーム出力

(vectorとかlistとか)コンテナの中身を確認したくてストリームに出力しようと思っても、

コンテナ自体にはoperator<<が定義されていないのでそのままostreamに出力することはできません。

だからといってコンテナの要素をイテレータでたどるために毎回

for(my_container_t::iterator it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it;
}

とか書くのは面倒です。

かといって上のコードをそのまま関数にするのもstd::coutしか使用できないってことで汎用性がないうえ、

せっかくのoperator<<の仕組みを使えないのは残念です。

(他にはstd::ostream_iteratorを使う方法

std::copy(vec.begin(), vec.end(), std::ostream_iterator<my_container_t::value_type>(std::cout));

もありますね。)

コンテナの要素をよしなに出力してくれて、出力先は好きなostreamを使えて

operator<<の形で書ける何かがあると嬉しいですね。

to_ostream_outputtableというクラスを作りました。

こいつを使えば、コンテナをストリームに出力可能にすることができます。

#include <boost/test/minimal.hpp>

#include <iostream>
#include <list>
#include <string>
#include <sstream>
#include <vector>

#include <hwm/to_ostream_outputtable.hpp>

struct TestClass
{
    int         age_;
    std::wstring name_;
    TestClass(int age, std::wstring const &name)
        :   age_(age)
        ,   name_(name)
    {}

    template<class CharType>
    friend
        std::basic_ostream<
            CharType,
            std::char_traits<CharType>
        > & 
        operator<<(
            std::basic_ostream<
                CharType,
                std::char_traits<CharType>
            > &os,
            TestClass const &t )
    {
        return
            os << L"{age : " << t.age_ << L", name : " << t.name_ << L"}";
    }
};

int test_main(int argc, char **argv)
{
    std::vector<std::string> strings;
    strings.push_back("hello");
    strings.push_back("world");
    strings.push_back("!");

    std::list<TestClass> daughters;
    daughters.push_back(TestClass(14, L"空"));
    daughters.push_back(TestClass(10, L"美羽"));
    daughters.push_back(TestClass(3, L"ひな"));

    std::stringstream ss;
    std::wstringstream ws;

    ss << hwm::to_os << strings;
	
    //(0番目の要素, 1番目の要素, 2番目の要素, ...)という風に出力される。
    BOOST_CHECK(ss.str() == "(hello, world, !)");

    //windowsならワイド文字列のwcoutに必要になるかも。それと他の環境のことは知らない。
    setlocale(LC_ALL, "japanese");

   //wide文字列用のostreamにもそのまま使える。
    std::wcout << L"ハッピハッピガー" << hwm::to_os << daughters << L"ハッピハッピガー" << std::endl;
    //ハッピハッピガー({age : 14, name : 空}, {age : 10, name : 美羽}, {age : 3, name : ひな})ハッピハッピガー
	
    return 0;
}

hwm/to_ostream_outputtable.hpp

#ifndef HWM_TOOSTREAMOUTPUTTABLE_HPP
#define HWM_TOOSTREAMOUTPUTTABLE_HPP

#include <boost/range/iterator.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>

namespace hwm {

namespace ostream_outputtable_detail {

    template<class CharType>
    struct ostream_output_impl
    {
        typedef
            std::basic_ostream<
                CharType,
                std::char_traits<CharType>
            >
        ostream_type;

        ostream_output_impl(ostream_type &os)
            :   os_(os)
        {}

        template<class Container>
        ostream_type & operator<< (Container &ct)
        {
            typedef typename boost::range_iterator<Container>::type iterator;

            iterator it         = boost::begin(ct);
            iterator const end  = boost::end(ct);

            if(it == end) {
                os_ << "()";
            } else {
                os_ << "(";
                for( ; ; ) {
                    os_ << *it;
                    ++it;
                    if(it != end) {
                        os_ << ", ";
                    } else {
                        os_ << ")";
                        break;
                    }
                }
            }
            return os_;
        }

        ostream_type &os_;
    };

    struct to_ostream_outputtable
    {
        template<class CharType>
        friend
        ostream_output_impl<CharType>
            operator<< (
                std::basic_ostream<
                    CharType,
                    std::char_traits<CharType>
                > &os,
                to_ostream_outputtable const &t )
        {
            return ostream_output_impl<CharType>(os);
        }
    };
}   //namespace ostream_outputtable_detail

static ostream_outputtable_detail::to_ostream_outputtable const to_os = {};

}   //namespace hwm

#endif  //HWM_TOOSTREAMOUTPUTTABLE_HPP

ostreamによしなに出力したいだけなので、Pythonとかのタプルの形式で決め打ちしています

他の形式で出力するためにポリシークラスを用意したりするともっと便利になるかもですね、でも面倒なのでやってません。

直後に来るコンテナを出力したあとはただの元のostreamに戻るので、

複数のコンテナを出力するときは毎回コンテナの直前にhwm::to_osを置くようにします。

それから、本当はストリーム側じゃなくてコンテナ側をいじってostreamに出力可能なようにする方が行儀がいいのかも・・・?

2012-01-07

Sapporo.cpp 新年会 2012を開催しました。

Sapporo.cpp 新年会 2012 : ATND

皆であつまってC++の話とか、去年の活動振り返ったりとか、今年もOSCで何か出来たらいいねーとか、そんな色んな話をしてきました。

新年早々お酒も呑めてすごく楽しかったです。

2011-12-31

今年のまとめ

DTMサークルを作って2年が、札幌C++勉強会/Sapporo.cppを作って1年が経ちました。

今年はいろいろと激動の1年でした。

忘れられない1年でしたが、その中で忘れてしまうこともあると思って、でも忘れたくないことがいっぱいあるので、覚えていてかつ書ける範囲で、いくつかここに書いておきたいなと思いました。

サークル編

今年は、サークル発足から2年ということで、去年は出来なかったことがいっぱい出来ました。

同人イベントにCDアルバム製作して出店したり、学祭でDJっぽいことやったり、みんなで課題に沿った曲作って集めるコンペやってみたり、サークル内でDTM講習会やったり。

特にイベント出店の時には、サークルや各メンバーに面白い出会いも有ったりして、非常に有意義だったと思います。

勉強会編

去年の12月におこなったBoost.忘年会2010@sapporo : ATNDから生まれた、札幌C++勉強会/Sapporo.cppは、去年12月の発足からようやく1年が経ちました。はじめ、勉強会運営は色々と分からないことだらけでしたが、一緒に考えて準備してくれる、運営メンバーと勉強会メンバー、それから、お世話になっている他の札幌の勉強会の先輩方の協力で、この1年目を迎えることが出来ました。


さらっと今年有ったことを書いておくと、


2/26に札幌C++勉強会#1 : ATNDを開催しました。

初めての勉強会開催でした。

このときは、東京で開催されたBoost.勉強会 #4のストリーム配信を札幌で見ながら、Boost.勉強会に参加しているみたいな気持ちを味わって、さらにその後懇親会なんかもやっちゃおうぜっていう企画でした。

札幌C++勉強会#1を開催しました! - How to disappear completely

6/11にオープンソースカンファレンス2011 Hokkaido - オープンソースの文化祭!を開催しました。

OSC 2011 Hokkaidoセミナーとブース展示しました。

前年には一般参加者として見てくるだけで終わってしまったOSCだったのですが、せっかく勉強会を発足したのだし、せっかくC++0xもホットな話題になっている時期だったので、今年は勉強会としてセミナーとブースで参加することにしました。

OSC2011 Hokkaidoに参加しました(喋ってきました)! - How to disappear completely


OSCに向けたSapporo.cppの打ち合わせの時、ブースでなにか小冊子作って配ってみようという事になりました。話し合いを進めるうちに、@さんに特別寄稿をお願いすることになり、@さんにも記事を書いていただく事になり、@さんにレビューに入ってもらうことが出来たりして、当初の予想より、かなりしっかりした小冊子を創り上げることが出来ました。

制作班は調整で深夜まで打ち合わせしたりだとか、みんな本当にお疲れさまでした!

zakさんもお忙しかった時期にも関わらずレビューをしていただきありがとうございました!

プログラミングの魔導少女 - sapporo.cpp


小冊子の作成に続いて、セミナー講演の準備もしました。内容は、当時もうすぐ規格が採択されるというC++0xの紹介でした。C++C++0xになることで、より便利になりますよ、と。

このときは@さんのおかげでUST配信することが出来ました。ありがとうございます!会場も、立ち見が出るほどの賑わいとなり、とても嬉しかったです。徹夜で@さんの研究室にこもってみんなで資料作成した甲斐がありました。あと、あれだけの人数の前で、自分でちゃんと話をするっていうことを経験出来てよかったです。特に質問の時間とかすごく楽しかったw

(筑波から!)@さんも札幌に遊びに来ていただいて、2011年C++erエンカウント祭りの幕開けでした。


7/3に札幌C++勉強会#2 : ATNDを開催しました。

Sapporo.cppとして、初めてのちゃんとした勉強会を開催しました。

OSCでも開催を宣伝した効果からか、22名という予想を超える数の参加者に参加していただくことが出来ました。初めてのちゃんとした形での勉強会開催で、今から思うと、もう少し工夫しておけばよかったとか、当日もっとこうすればよかったなー、なんていう点も有ったりします。。。

でも、勉強会の準備とはこういう感じ、発表とはこういう感じ、みたいなことを経験できてとてもよかったです。

札幌C++勉強会#2を開催しました! - How to disappear completely


Boost.勉強会を札幌で開催する件を本格的に進め始めたのは札幌C++勉強会#2が終わったこの時期くらいからだったかなー?

8/12にBoost.Beer 札幌 : ATNDを開催しました。

#2のあとは、ビアガーデンBoost.Beer 札幌を開催しました。小冊子に記事を執筆していただいたり、OSCの時にお会いした@さんとちゃんとお話しすることができて面白かったです。酒の肴はストロウストルップのプログラミング入門でした。

Boost.Beer 札幌を開催した。 - How to disappear completely


10/1に札幌C++勉強会 #3 : ATNDを開催しました。

Boost.勉強会の準備を進めつつ、その開催に向けて、その前に、Boostに入門できることを目的とした勉強会を開催しようということで、時期的にもちょうといい所で札幌C++勉強会 #3 : ATNDを開催しました。実際に触ってもらいながらBoostの便利さを知ってもらおうという考えで準備を進めてみましたが、これがなかなか難航して、参加者体験型の勉強会の難しさを思い知ることとなりました。


11/4にBoost.勉強会 #6 札幌 前夜祭 : ATNDを開催しました。

Boost.勉強会に先立って、前日から札幌入りしている怖い方々とジンギスカン食べてきました。

OSCの時にFlastさんにはお会いしていましたが、ここで@さんと@さんに初めてお会いしました。闇の話とかC++erの話とか、Flastさんのネットワークの話とか、「全ての道はCry's Diaryに続いている」とか、「そこは@が5年前に通った道だ」とか楽しいお話ができました。


11/5にBoost.勉強会 #6 札幌 : ATNDを開催しました。

Boost.勉強会初参加にして、札幌での開催を担当させていただきました。

本州からもたくさんの方に参加していただきました。

中には、Boost.勉強会だと意識しないで、「札幌でなにかC++の祭りがあるらしい」的な感じで参加してくださった方もいらっしゃったようです。

この開催に当たっては@たんにネットワークUSTを担当していただきました。Sapporo.cppのメンバーにも準備に尽力していただきました。

僕にとって夢であったBoost.勉強会の参加や道外のC++erとの対面などが一気に実現した、思い出に残る勉強会の開催となりました。嬉しいです。

運営に携わってくださった皆様、参加してくださった皆様ありがとうございます。

Boost.勉強会 #6 札幌を開催しました! - How to disappear completely

Boost.勉強会 #6 札幌を開催しました!2 - How to disappear completely


11/10にεπιστημηさんを囲む会 : ATNDを開催しました。

Boost.勉強会が終わった直後だったかな?に、@さんから、@さんが札幌にいらっしゃるので、Sapporo.cppのみなさんも交えて飲み会でもやりませんかというお話をいただきました。エピさんといえば「C++の設計と進化」(通称D&E)の監修や、アキラ先生と共著の「C++テンプレートテクニック」や、Cppllなどで著名な方で、以前から機会があればお会いしたいなーなんて思ってたのですが、こんなに早く実現するとは・・・!

C++98規格採択以前のC++の話など面白い話を聞かせていただきました。類まれな機会をいただきまして、@さんと@さんありがとうございました。

επιστημηさんを囲む会を開催しました。 - How to disappear completely

12/10に関数型都市忘年会 : ATNDを開催しました。

Boost.勉強会が終わってから個人的にちょっと忙しくて割とバタバタして落ち着きないまま当日まで迎えた関数型都市忘年会でした。

関数型プログラミングを共通項にしたいろんな方の発表を見せていただくことが出来てよかったです。忘年会ですしゆるふわでしたね!☆(ゝω・)vキャピ

僕もC++テンプレート入門という内容で発表しました。内容はもっとちゃんと作り込むべきでした。でも、開催も発表も懇親会も、とても楽しかったです!

関数型都市忘年会を開催しました! - How to disappear completely

関数型都市忘年会 & Hokkaido.pm 合同懇親会を開催しました。 - How to disappear completely


12/29に札幌で中3女子と忘年C++焼肉 : ATNDを開催しました。

岡山から中3女子陶芸C++erの@さんが札幌に来るというので、急遽忘年会開催することとなりました!

ボレロさんと札幌のご友人と、Sapporo.cppのメンバーで焼肉に行ってきました。

constexprと中3女子とほもせっくすトークでこれはひどいとっても楽しかったです。

焼き肉おいしかったし、中3女子にも会えたし、今年の締めに非常に面白い企画となりました。

次回のBoost.勉強会でもお会いできるといいですね

カラオケも行きたいです。


まとめ

という感じで、今年は、勉強会運営を通じて、人前で発表したり、普段お会いできない人に会えたり、C++erプログラマー達といっぱい一緒に活動が出来たり、なかなか出来ないような経験を沢山することが出来ました。Sapporo.cppという組織で活動できてうれしいと思います。

来年はもっと、運営に興味がある人がいたら一緒にやりたいと思うのと、勉強会の内容の質を向上して、そして対象者レベルを広くカバーできるようにして、C++Sapporo.cppに興味を持ってくれる人が増えたらいいなと思います。

今年一年お世話になった皆様(特に日頃お世話になっている@さん、@さん、@さん)、大変ありがとうございました。

来年もまたよろしくおねがいします。

2011-12-19

出現するconstと消失するconst

この記事はC++11 Advent Calendar 2011 : ATNDの参加記事です。

僕はあんまりC++11を使いこんでないため、C++11のノウハウが無いので、C++11にも関連するようなC++の面白い記事を翻訳していつでも自分が読めるようにしておいてお茶をにごすことにしますね。

C++NextというC++関連のレベルの高い記事を掲載しているブログがあります。

今回はそこからAppearing and Disappearing consts in C++ « C++Nextという記事を選びました。

あと、翻訳の質は保証されません。ミスは指摘していただけると嬉しいです。

この記事はC++11の規格採択以前に公開されたものなので、C++11の事をC++0xと呼んでいます。文中では適宜C++11と読み替えてください。


"Appearing and Disappearing consts in C++"

C++Nextは幸運にもScott Meyersの許可を得て彼のこの記事を再版することが出来ました。

ありがとう、Scott!

もしあなたがC++で"int i;"と書いたなら、iの型は明らかにintです。もし"const int i;"と書いたなら、同様にiの型はconst intになるでしょう。多くの場合において、そういった型は、確かにそれらのみえるまま(what they seem to be)の型なのですが、時々そうでないものもあります。ある状況のもとでは、C++はnon-constな型をconstとして扱い、また他の状況ではconstな型をnon-constとして扱うこともあります。

constがいつ現れていつ消えるのか、それを理解することは、C++の内部の挙動についての洞察に役立ち、この言語をより効率的に使用することを可能にするでしょう。

このArticleでは、「変数の有効な型が、その明らかな型と違うのはなぜか、どのようにしてそうなるのか」ということをあなたが理解しやすいような視点で、type declarationとdeductionを、現行の規格のC++と、現在校正中の来るべき新規格(C++0x)の両方で、さまざまな側面から吟味していきます。

もしあなたが、テンプレート引数の推論の基本的なルールに精通していたり、lvalueとrvalueの違いを分かっていたり、lvalueとrvalue参照やauto変数ラムダ式といった新しいC++0xのfeatureにふれているなら、このArticleは最も有用なものだと気づくでしょう。

いま

int i;

という宣言があったとき、iの型はなんでしょうか?別に引っ掛け問題ではありません、この型はintです。

しかし、iをstructの中においた場合はどうでしょう。(もしくはclassの中に - どちらでも変わらないですが)

struct S{
    int i;
};

S::iの型はなんでしょうか?これもintでしょうか?その通り。ただ、S::iのアドレスを取るとき、あなたはint*ではなく、メンバーポインタ(つまりこの場合はint S::*)を受け取るので、structの中と外のintは完全に同じというわけではありません。しかし、やはりS::iの型はintです。


ではたとえば、私たちがこのstructのポインタを持っている場合、

S *ps;

ps->iの型はなんでしょうか?

(実際、この例ではpsは何を指していなくても関係ありません。たとえクラッシュするような未定義動作を生むようなコードだとしても、式ps->iは一つの型を持っています。)

これもまだ引っ掛け問題ではありません。ps->iの型はintです。

しかし、もし私たちが持っているのがconstへのポインタだった場合は?

const S* pcs;

pcs->iの型はなんでしょうか。なんだかよく分かりませんね。

pcs->iはSの中のint iを指しています。そしてS::iの型はintということについて先ほど考えを一致させました。

しかし、pcsを通してS::iにアクセスするとき、Sはconstなstructになっているので、したがってS::iもconstになるでしょう。

という訳で、pcs->iの型はintではなく、const intになるべきだと考えることが出来ます。


現行のC++(すなわち、1998年に採択された国際標準で定義され、2003年に少しだけ改訂し、今後はC++98と呼ばれるような言語としてのC++)では、pcs->iの型はconst intであり、お話は大体これで終わりです。"新しい"C++(すなわち、今年中にも国際標準に採択されるであろう、通称C++0xと呼ばれる言語)でも、pcs->iの型は同じくconst intであることが、後方互換性のため、保証されています。ただし、この話にはさらにニュアンスが含まれています。


decltype

C++0xからdecltypeが導入されました。これは、compile時に式の型を問い合わせる方法の1つです。

例えば、iの宣言がすでにあるとき、decltype(i)はintになります。

しかし、pcs->iにdecltypeを適用した場合は何が起こるでしょうか?標準化委員会は単にこの型をconst intにしてしまうということも可能でした。

でも、この型をintだとする考えも、合理的でないわけではありません(つまり、S::iの型を、私たちは結局、Sの中のiについて言及しているのだとすれば)。

そして結果的に、変数へのアクセスの式に依存せず、変数を宣言した型を問い合わせることが出来るということは有用だということも分かります。

また、合理的で相反する選択が与えられた時、このC++という言語は、しばしば椅子に深く腰掛けて、あなたに選択をさせようとするでしょう。それはまさに、このケースのように。


あなたが選択したのが、典型的なC++の方法の場合。変数の名前にdecltypeを適用することで、

その変数を宣言した型を取得することができます。例えばこれは、xとyをi、そしてS::iと同じ型で宣言しています。

decltype(i) x;         //x is same type as i, i.e., int
decltype(pcs->i) y;    //y is same type as S::i, i.e., int

それに対して、変数の名前ではなく、式に対してdecltypeを適用した場合は、その式の有効な型(effective type)を取得することが出来ます。

もしその式がlvalueに対応するものなら、decltypeは常にlvalueの参照を返します。すぐ後で例をお見せしますが、その前に私は、

"i"は変数の名前であるのに対して、"(i)"は式であって、変数の名前ではないということについて言及しておかなければなりません。言い換えれば、ある変数の周りにparenthesesを放り投げてやれば、それは、その変数と同じ実行時の値を持つような式になります。

しかし、コンパイル時にはそれはただの式であり、変数の名前ではありません。

これは重要なことです。なぜなら、これの意味するところは、もしあなたがpcsを通してS::iの型を知りたいと思った時、もしpcs->iの周りにparenthesesを付けて問い合わせたなら、decltypeは次の型を返します:

decltype((pcs->i))    // type returnd by decltype is const int& (see also
                      // discussion below)

parenthesesを付けなかった方と比べてみましょう

decltype(pcs->i)     // type returned by decltype is int

私が言及したように、decltypeで、変数の名前ではなく式の型を問い合わせると、その型がlvalueならば、decltypeはlvalueの参照を返します。それが、decltype((pcs->i))がconst int&を返す理由です : pcs->iはlvalueですから。

もしあなたが(constあるいはnon-constでも)intを返す関数を持っているとき、その戻り値の型はrvalueです。そして、decltypeは、その関数呼び出しの戻り値の型をreference-qualifyingしないことで、それを表します。

const int f();      // function returning an rvalue

decltype(f()) x = 0; // type returned by decltype for a call to f is
                     // const int (not const int&), so this declares x
                     // of type const int and initializes it to 0

もしあなたがheavy-dutyなlibrary writerでもない限りは、このような微妙なdecltypeの詳細について心配する必要はないかも知れません。ただ、変数の宣言の型とその変数アクセスする式の型に違いがあることを知っておくことは確かに価値があることでしょう。そして、それらを区別しようとするとき、decltypeがその助けになるということを知っておくのも、同様に価値があります。


Type Deduction

あなたは、変数の宣言の型と変数へのアクセスの式の型のこの違いは、C++0xからの新しいものと考えたかもしれません。しかし、この違いは本質的にC++98から存在しています。関数テンプレート型推論の時に何が起こるか考えてみましょう。

template
void f(T p);

int i;
const int ci = 0;
const int *pci = &i;
f(i); // calls f, i.e., T is int
f(ci); // also calls f, i.e., T is int
f(*pci); // call f once again, i.e., T is still int

top-levelのconstはtemplateの型推論の時に取り去られるので*1、型パラメータTはintからもconst intからもintに推論されます。また、これは変数でも式でも同様であり、さらにC++98でもC++0xでも同様です。

つまり、あなたが特定の型の式をtemplateに渡したとき、templateによってみなされる(すなわち推論される)型は、式の型とは違うものになりえます。


C++0xでのauto変数型推論は本質的に、templateパラメータのそれと同じです。(私の知る限り、唯一の違いは、auto変数がinitializer listから推論されうるのに対して、template parameterの方はそうではないということだけです。)

なので、以下の宣言はどれも(決してconst intではなく)、int型の変数を宣言しています:

auto a1 = i;
auto a2 = ci;
auto a3 = *pci;
auto a4 = pcs->i;

templateパラメータとauto変数型推論の時に、top-levelのconstのみが除かれます。ポインタか参照を引数に取る関数テンプレートがある時、ポインタの指す先、あるいは、参照が参照している先のconst性はそのまま残ります。

template<typename T>
void f(T &p);

int i;
const int ci = 0;
const int *pci = &i;

f(i);    // as before, calls f<int>, i.e., T is int
f(ci);   // now calls f<const int>, i.e., T is const int
f(*pci); // also calls f<const int>, i.e., T is const int

この振る舞いは旧聞であり、C++98にもC++0xにも適用することが出来ます。auto変数での対応する振る舞いは、もちろんC++0xからですね。


Lambda Expression

C++0xの中で洒落たnew featuresの中にlambda expressionがあります。lambda expressionは関数オブジェクトを生成します。

lambdaから生成されたobjectはclosureとして知られ、生成されたオブジェクトのクラスはclosure typeとして知られています。ローカル変数がlambdaの中にby valueでキャプチャーされたとき、そのlambda用に生成されるclosure typeは、キャプチャーするローカル変数に対応するdata memberを持ち、そのdata memberの型は、キャプチャーする変数の型です。


もしあなたが、C++0xのlambdaに精通していなければ、私はただ回りくどい書き方をしてしまっただけですね。はい。でもちょっと待ってください。明快にするためにベストを尽くしますので。

私があなたに注意して欲しい点は、私が言った、クラスの中のdata memberの型はlocal変数の型と同じということです。それはつまり、"local変数と同じ型"が何を意味しているのかを知る必要があるということです。もしこれが素晴らしくシンプルなら、私はそれについて何も書いたりしないのですが。


でもまず最初にlambdaとclosureとclosure typeについて簡単に説明させてください。私がlambda式の所をハイライトしたこの関数を考えてみましょう。*2

void f(const std::vector<int> &v)
{
    int offset = 4;
    std::for_each(v.cbegin(), v.cend(),
        [=](int i){ std::cout << i + offset; });
}

このlambdaはコンパイラに次のようなクラスを生成させます。

class SomeMagicNameYouNeedNotKnow {
 public:
    SomeMagicNameYouNeedNotKnow(int p_offset): m_offset(p_offset) {}
    void operator()(int i) const { std::cout << i + m_offset; }
 private:
    int m_offset;
};

このクラスがclosure typeです。fの中で、std::for_eachの呼び出しは、まるでこの(closureの)型のobjectがlambda式の代わりに使われているようにみなされます。つまり、std::for_eachの呼び出しは、まるでこのように書いたようなものです。

std::for_each(v.cbegin(), v.cend(), SomeMagicNameYouNeedNotKnow(offset));

ここで私たちにとって興味深いのは、local変数offsetと、closure typeの中でそれに対応する、私がm_offsetと呼んだようなdata memberの関係です。(コンパイラはdata memberに好き勝手な名前をつけることが可能なので、私のm_offsetの使用の中から何かを読み取ったりしないでください。)

与えられたoffsetがint型であるとき、m_offsetの型もint型であるのは特に驚くべきことではありません。

しかし、注意してみてください、lambdaからのclosure type生成のルールにしたがって、closure typeの中のoperator()はconstとして宣言されています。

これはつまり、operator()のbody - lambdaのbodyと同じもの - で、m_offsetはconstだということです。実際的な言い方をすれば、m_offsetの型はconst intであり、そして、もしあなたがそれをlambdaの中から変更しようとすると、あなたのコンパイラは代わりに不機嫌な言葉をくれることでしょう。

std::for_each(v.cbegin(), v.cend(),
              [=](int i) { std::cout << i + offset++; });             // error!

もしあなたが本当に、offsetのコピーを変更したいと思うなら(すなわち、本当にm_offsetを変更したいと思うなら)、

mutable lambdaを使用する必要があります。*3

このlambdaはoperator()関数constで無いようなclosure typeを生成します。

std::for_each(v.cbegin(), v.cend(),
              [=](int i) mutable { std::cout << i + offset++; });     // okay

変数をby valueでキャプチャーするとき(すなわち、変数のコピーをキャプチャーするとき)、最初にlambdaにmutableを付けていなければ、コピーしたものの型にconstが付いてしまいます。


関数fの中で、offsetは決して変更されなかったので、あなたはoffsetをconstとして宣言することに決めました。

これは賞賛すべき習慣であり、さらに、期待通り、それはnon-mutableなlambdaには何の影響も及ぼしません。

void f(const std::vector<int>& v)
{
    const int offset = 4;
    std::for_each(v.cbegin(), v.cend(),
                  [=](int i) { std::cout << i + offset; });           // fine
}

ああ、なんということでしょう。mutable lambdaの方では話が違うようです。

void f(const std::vector<int>& v)
{
    const int offset = 4;
    std::for_each(v.cbegin(), v.cend(),
                  [=](int i) mutable { std::cout << i + offset++; }); // error!
}

"えらー?えらー?!,"そんなふうにあなたは言うでしょうか? はい、エラーです。

このコードはコンパイル出来ません。なぜなら、あなたがconst local変数をキャプチャーしたとき、closureの中の変数のコピーもまたconstだからです: ローカル変数const性はclosureの中のコピーの型にもコピーされるのです。最後に出てきたlambdaのclosure typeは本質的にはこのようになります:

class SomeMagicNameYouNeedNotKnow {
private:
    const int m_offset;
public:
    SomeMagicNameYouNeedNotKnow(int p_offset): m_offset(p_offset){}
    void operator()(int i) { std::cout << i + m_offset++; }          // error!
};

これは、なぜm_offsetをインクリメントすることが出来なかったかを明らかにしてくれるでしょう。その上、m_offsetがconstとして定義されている(すなわち、point of definitionでconstとして宣言されている)という事実は、あなたがcastを取り払うようなどんなキャストもすることが出来ず、変更可能であるということをあてにすることが出来ないことを意味します。なぜなら、constとして定義されたオブジェクトの値を、castによってconstを取り払って変更しようとした結果は未定義だからです。


なぜ、autoやtemplateパラメータ型推論ではtop-levelのconst性は無視されるのに、closure typeのdata memberの型を推論するときには、もとの型のtop-levelのconstが残っているのか、疑問に思う人もいるでしょう。

あるいはまた、なぜ、この振る舞いをオーバーライドする方法がないのか、疑問に思う人もいるでしょう。すなわち、by-valueでキャプチャーされるローカル変数に対する、closure typeのdata memberが作られるときに、コンパイラに対して、top-levelのconstを無視するように教えたりする方法が。私も、自分で疑問に思いました。しかし、それらに隠された公式な理由は見つけ出すことが出来ませんでした。しかし、もしこのようなコードが有効な場合、

void f(const std::vector<int>& v)
{
    const int offset = 4;
    std::for_each(v.cbegin(), v.cend(),
                  [=](int i) mutable { std::cout << i + offset++; }); // not legal, but
}

実際は操作されているのはclosureの中にあるoffsetのコピーであるのに、a casual readerが、const local変数のoffsetが変更されていると誤って結論づけてしまうということが確認されています。

類似した関係は、なぜnon-mutableなlambdaによって生成されたclosure classのoperator()がconstなのかという理由にもなりえます: lambdaが本当はlocal変数のコピーを変更しているのに、もとのlocal変数を変更していると考えてしまうことから、人々を守るためです:

void f(const std::vector& v)
{
    int offset = 4;                                 // now not const
    std::for_each(v.cbegin(), v.cend(),
        [=](int i) { std::cout << i + offset++; }); // now not mutable, yet
} // the code is still invalid

もし、このコードがコンパイル出来たとして、これはlocal変数のoffsetは変更しません。しかし、誰かがこれをそうでないと思って読んでしまうなんてことは想像に難くありません。


私個人としては、この推理が説得力のあるものかどうかは分かりません。なぜならlambdaの作者がもうはっきりと、結果として生じるclosureがoffsetのcopyを含むように要求してしまっていますし、そして私は、このコードの読者に対して、留意を期待することも出来ると思うからです。

しかし、私が思うに、それは重要なことではありません。重要なことは、lambdaの中で使用される変数がコピーでキャプチャーされるときの型の扱いを、このルールが支配しているということです。


Summary

以下にある、イタリック体の用語は、私が勝手に作ったものです、それらの標準的なものがなかったので。

  • オブジェクトdeclared typeとは、そのオブジェクトのpoint of declarationの時に持っている型のことです。これは、私たちがその型について普通に考えるままのものです。
  • あるオブジェクトeffective typeとは、特定の式を通してアクセスするときに持っている型のことです。
  • C++0xにおいて、decltypeは、object declared typeとeffective typeを区別するために使用出来ます。C++98には対応する標準の機能は存在しません。しかし、コンパイラ拡張(例えばgccのtypeof operator)や、Boost.Typeofのようなライブラリを通して、そのほとんどの機能を得ることができます。
  • templateパラメータと、C++0xの auto変数型推論の間に、top-levelのconstとvolatileは無視されます。
  • lambda式の中でby valueでキャプチャーされるコピーの変数の宣言の型は、オリジナルの変数の型と同じconst性を持っています。non-mutableなlambdaの中では、コピーされた変数の型のeffective typeはつねにconstです。なぜなら、closure classのoperator()はconst member関数だからです。

Acknowledgements

Walter BrightとBatosz MilewskiはこのArticleの原稿に有益なコメントを提供してくれました。そして、Eric Nieblerは寛大にも、このArticleをC++Nextで発表する準備に賛成してくれました。

Displaying Types

残念ながら、C++には変数や式の型を表示する標準的な方法はありません。私たちが持っている最も類似した方法は、typeidから取得できる、std::type_infoクラスのnameメンバ関数です。これは時々かなり良く働くのですが、

int i;
std::cout << typeid(i).name(); // produces "int" under Visual C++,
                               // "i" under gcc.

時々は、そうではありません。

const int ci = 0;
std::cout << typeid(ci).name(); // same output as above, i.e.,
                                // const is omitted

あなたはこれを、テンプレートの技術を使って型を分解し、型の構成要素それぞれの文字列表現を生成して、それらを表示用の形式に再構成することで、より良くすることが出来るでしょう。

もしあなたの運が良ければ、私がUsenetのcomp.lang.c++ニュースグループでこれについて質問した時のように、すでにこれを作り上げた人を見つけることが出来るかもしれません。

Marc ClisseはPrinttype templateを提示してくれました。これは、以下のコードをgccコンパイルしたときに、あなたが期待する出力のほとんどを生成します。(この例はMarcのコードから引用しました。)

struct Person{};
std::cout << Printtype<unsigned long*const*volatile*&()>().name();
std::cout << Printtype<void(*&)(int(void),Person&)>().name();
std::cout << Printtype<const Person&(...)>().name();
std::cout << Printtype<long double(volatile int*const,...)>().name();
std::cout << Printtype<int (Person::**)(double)>().name();
std::cout << Printtype<const volatile short Person::*>().name();

MarcのアプローチはC++0xの機能であるvariadic templateを使用しています。variadic templateは現在、私の知る限り、gccでしかサポートされてませんが、彼のコードは少々の変更で、C++98準拠の多くのコンパイラでも同じように働くだろうと私は考えています。

MarcのPrinttype templateは、以下のreferenceから取得可能です。

Further Information

  • C++0x entry at Wikipedia. A nice, readable, seemingly up-to-date overview of language and library features introduced by C++0x.
  • Working Draft, Standard for Programming Language C++, ISO/IEC JTC1/SC22/WG21 Standardization Committee, Document N3225, 27 November 2010. This is the nearly-final C++0x draft standard. It’s not an easy read, but you’ll find definitive descriptions of decltype in section 7.1.6.2 (paragraph 4), of auto in section 7.1.6.4, and of lambda expressions in section 5.1.2.
  • decltype entry at Wikipedia. A nice, readable, seemingly up-to-date overview of C++0x’s decltype feature.
  • Decltype (revision 5), Jaako Järki et al, ISO/IEC JTC1/SC22/WG21 Standardization Committee Document N1978, 24 April 2006. Unlike the draft C++ standard, this document provides background and motivation for decltype, but the specification for decltype was modified after this document was written, so the specification for decltype in the draft standard does not exactly match what this document describes.
  • Referring to a Type with typeof, GCC Manual. Describes the typeof extension.
  • Boost.Typeof, Arkadiy Vertleyb and Peder Holt. Describes the Boost.Typeof library.
  • Deducing the type of variable from its initializer expression, Jaako Järvi et al, ISO/IEC JTC1/SC22/WG21 Standardization Committee Document N1984, 6 April 2006. This document gives background and motivation for auto variables. The specification it proposes may not match that in the draft standard in every respect. (I have not compared them, but it’s common for details to be tweaked during standardization.
  • Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 4), Jaako Järvi et al, ISO/IEC JTC1/SC22/WG21 Standardization Committee Document N2550, 29 February 2008. This is a very brief summary of lambda expressions, but it references earlier standardization documents that provide more detailed background information. The specification for lambdas was revised several times during standardization.
  • Overview of the New C++ (C++0x), Scott Meyers, Artima Publishing, 16 August 2010 (most recent revision). Presentation materials from my professional training course in published form. Not as formal or comprehensive as the standardization documents above, but much easier to understand.
  • String representation of a type, Usenet newsgroup comp.lang.c++, 28 January 2011 (initial post). Discussion of how to produce displayable representations of types. Includes link to Marc Glisse’s solution.

次回は

わてすさんのゼロから作る! template で構文解析メタプログラム | メモ代わり。です。

よろしくお願いします。

*1:top-levelのvolatileもtop-levelのconstと同じように扱われます。しかし、"top-levelのconstあるいはvolatile"と繰り返しいうのを避け、コメントをconstのみに制限しました。そして、C++はvolatileも同様に扱うというあなたの理解をあてにしています

*2:訳注:原文では下のコードスニペットでラムダ式ハイライトされている

*3:そうするかもしくは、変更したい時に毎回offsetのコピーをcastしてconstを取り払ってもいいでしょう。mutable lambdaを使うほうがより理解しやすいと思います。