組み込み型の一時オブジェクトは変更不可らしい
Exceptional C++を読んでたら
"C++ language doesn't allow you to modify temporaries of builtin type."
(適当訳:組み込み型の一時オブジェクトは変更不可)
という一文があったのでいろいろ試してみた。
まず
int func1() { return 1; } int main() { std::cout << ++func1() << std::endl; return 0; }
をやってみると
error C2105: '++' には左辺値が必要です。
となって確かにコンパイルできない
次にユーザー定義クラスで試してみると
struct Test { int arg; Test(int i) : arg(i){} Test& operator+(const int i) { arg += i; return *this; } Test& operator++() { arg++; return *this; } friend std::ostream& operator<<(std::ostream& os,Test &obj) { os << obj.arg; return os; } }; Test func2() { return Test(1); } int main() { std::cout << ++func2() << std::endl; return 0; }
これだとユーザー定義クラスなので通る
ここで、さっきのfunc1がコンパイルに失敗したときのエラーメッセージを思い出してみると
error C2105: '++' には左辺値が必要です。
となっていたので、確か左辺値って代入可能な値だったよなーということで
intのリファレンスを返す関数ならどうだろうと試してみると
struct Test { int arg; int& refArg; Test(int i) : arg(i) , refArg(arg){} Test& operator+(const int i) { arg += i; return *this; } Test& operator++() { arg++; return *this; } friend std::ostream& operator<<(std::ostream& os,Test &obj) { os << obj.arg; return os; } int& memIntFun() { return arg; } }; int main() { std::cout << ++obj.memIntFun() << std::endl; return 0; }
これだと大丈夫らしい。
ちなみに、ユーザー定義クラスのリファレンスを返すような関数で得られた一時オブジェクトも、もちろん変更可能。
この辺をまとめてみると
どうやら「基本型の一時オブジェクトは変更不可」というよりも
単純に基本型の値返しは右辺値になって、右辺値は変更不可だから、結果として変更不可になる。
って意味っぽい。
ここまでやって、ユーザー定義クラスも非リファレンスの値返しは右辺値でいいんじゃないの?って思ったんだけど
int main() { //こういう意味のないコードもユーザー定義クラスは左辺値扱いなので通る std::cout << ++(func2() = Test(4)) << std::endl; return 0; }
ユーザー定義クラスだとオペレータオーバーロードされた場合に中で何をしてるかよくわからないしとりあえず左辺値 ってことなのかな
まあなにより左辺値と右辺値のイメージが曖昧すぎて何か間違ってる気がしなくもないんだけど
boost::bindが返してくるファンクタ
boost::bindが返してくるファンクタの引数を過剰に与えるといらない部分は無視されるらしい(というか過剰に渡しても普通に動く)
#include <iostream> #include <string> #include <boost/bind.hpp> struct Functor { typedef void result_type; result_type operator()(std::string message){ std::cout << message << std::endl; } }; int main() { Functor()("Hello");//通常の関数呼び出し //Functor()("Hello","Invailed Argument"); //引数の数が違うので呼び出せない //bindを使って関数を呼び出し boost::bind(Functor(),_1)("World"); boost::bind(Functor(),"Hello")(); //以下のどちらの呼び出しの方法でも過剰な引数が無視されるため実行できる boost::bind(Functor(),_1)("C++","Invailed Argument"); boost::bind(Functor(),"World")("Invailed Argument"); boost::bind(Functor(),_1)("1","2","3","4","5","6","7","8","9"); //boost::bind(Functor(),_1)("1","2","3","4","5","6","7","8","9","10");//引数は9個まで return 0; }
じゃあいったいbindは何を返しているんだ、ということでbindのソースのさわりの部分を見てみると
どうやら0〜9引数ごとにoperator()を書いておいて、渡された引数をリストにした後に、
boost::_bi::bind_tというクラスのコンストラクタにリストを渡して、そのbitd_tを返してきているらしい
このbind_tの中には実際のファンクタの引数の数とは関係なく、9引数までのoperator()が書かれているので、
多く引数を与えてもとりあえず呼び出すことができるみたい。
しかしboost::asioのチュートリアルはレベルが高い
こういう微妙な事をさも当たり前のように使うとは
仮想関数のアクセス指定子
どうやらC++はオーバーライドする際にアクセス指定子を好きなように書き換え放題らしい
具体的には
#include <iostream> class Base { public: virtual void func(){ std::cout << "Base" << std::endl; } }; class Deriverd : public Base { private: virtual void func(){ std::cout << "Deriverd" << std::endl; Base::func(); } }; int main() { Deriverd* p = new Deriverd(); Base* b = p; //p->func();//これは呼び出せない b->func(); return 0; }
こんな挙動をするって事
派生クラスでprivateにしたから安心! 外から呼ばれないね とか思ってると
基底クラスではpublicだったからポインタを介して呼ばれるというすごく嫌なことになりそう
どうやらC++の規格書によると、
・アクセス指定子は仮想関数をオーバーライドする際に特に制約はない
・アクセス制限のチェックは呼び出し時に唯一行われる(例え仮想関数であっても呼び出し時の静的な型による)
らしい。
たぶんこの仕様はコンパイラで仮想関数を実装する時に、各仮想関数の先頭に動的型に適合する関数へのジャンプコードを埋めるようなことができるように
するためなんだと思うんだけど(この実装なら仮想関数を実装するのに通常の関数呼び出しと、仮想関数の先頭への判定&ジャンプコード挿入でいけそう)
ちなみに今回上のコードを作って調べるまでは
「オーバーライドされる側のアクセス修飾子よりも範囲の広い物であればOK」だと思ってコード書いてました。
まあオーバーライドする関数がされる側よりも範囲の狭いアクセス修飾子つけちゃうとポインタを介して基底クラスの公開メソッドを呼べなくなるかなと思ってたので
まあ確かに仮想関数のアクセス制御は静的に、呼び出しは動的になんてしちゃえばこの問題は起きないけど・・・
他の言語はというと
Javaは今まで勘違いしてたように「オーバーライドされる側のアクセス修飾子よりも範囲の広い物であればOK」
C#はアクセス修飾子の変更は認めない らしい
まあC#の変更は認めない っていうのはoverrideでオーバーライドしたときのみ適用で
newで元メソッドを隠蔽した場合は好き勝手できるみたいだけど
なんかこう見てみるとやっぱりC#はJavaとC++のいいとこどりというか
Javaだとなんでもかんでも仮想関数扱いだから隠蔽できないし
C++だとデフォルトが非仮想だったり好き勝手に修飾子変えるれたりするし
その点C#ならoverrideとnewキーワードのおかげでその辺いろいろと柔軟性が出ていてよさそう
しかしC++に関してもVSが非仮想関数のオーバーライドしたときに赤の波線引いたりぐらいはしてくれても良いと思うんだけどなー
型レベルプログラミングの会とDB
- 型レベルプログラミングの会
関数型言語の勉強をしてから行くはずが時間が取れず、
当日の関数型言語系のセッションではいろいろと推測しながらさぐりさぐり聞いてる感じに
この前に行ったLLVM勉強会のyarv2llvmでもそうだったけれど
「本来想定されていないような使い方で言語を利用する」となかなかすさまじい事になるんだなということを再確認
(今回は型レベルプログラミングが想定されていないような言語で型レベル、yarvの時はRuby(動的言語)をLLVMbitCode(静的言語向け))
あのセッションの後だとC++がすごく綺麗な言語に見える不思議
あと関数型言語に関しては、数学的な議論がいろいろとされていて(よくわからなかった)違う世界を覗けておもしろかったと思う。
C++とDは知ってることも多かったんだけど、特にDに関しては、「C++からCの呪縛をといたらこんな事になっちゃいました」的な部分がいろいろと見れた。
- データベーススペシャリスト
たぶんというか、確実に午後2で落ちた。
午前はすごくよくできた。(24/25)
午後2はERモデリングを選ぶことに決めていたので、解答用紙を見た瞬間に問2を選択したんだけど
なんていうかここ数年のERの中で一番めんどくさかったんじゃないかと思う。
マスタ領域、トランザクション領域 とかって形で破線が引かれているわけでもなくかなり厳しかった。
思い返してみるとERが完成していない段階からテーブルを逐次書いていったりとか
解き方がいろいろと悪かったので、もし次回受けるのであればちゃんとERを完成させてからにしよう。
まあ次回受けるかはすごく微妙だけど(午前1免除じゃないし・・・)
あと今週のわんくま@東京は、ちょうど実家に帰る予定が出来たので行く予定です。
今度はconceptを使って戻り値指定のいらないbindっぽいことをしてみる
前回のエントリのコメントで、Callable conceptがあるよと教えてもらったので、
昨日書いたdecltypeに続き、conceptで似たことをしてみることにした。
conceptGCCでは、std::Callableを使おうとすると、まだ実装されてないよ。みたいなメッセージが出てくるので
ソースを見に行ったところ、こんな記述があった。
// TODO: cannot express Callable, which is variadic, so we settle // for Callable0, Callable1, etc.
なので、varadic templateを使ってないバージョンのCallableを使って書いてみると
↓みたいになった
#include <concepts> #include <iostream> #include <string> #include <boost/bind.hpp> template <typename F,typename Arg1> requires std::Callable1<F , Arg1 > F::result_type mybinder(F obj,Arg1 x) { obj(x); }; struct Functor { void operator()(std::string str){ std::cout << str << std::endl; } }; int main() { mybinder(Functor(),"Hello"); return 0; }
まあただ転送してるだけなのでbindとはほど遠いけど(ファンクタも返さないし)
次に、コンセプトは、エラーメッセージを読みやすくするのも目的の一つだったと思うので、
試しにエラーを吐かせようと一つファンクタを追加
struct InvalidFunctor { typedef bool result_type;//operator()の戻り値とは違う型をtypedef result_typeしてみる void operator()(std::string str){ std::cout << str << std::endl; } }; int main() { mybinder(Functor(),"Hello"); mybinder(InvalidFunctor(),"Hello"); return 0; }
しかし普通に動いた・・・
なんだかよくわからなくなってきたので、とりあえずtypeinfoでresult_typeの型を調べてみると
#include <concepts> #include <iostream> #include <string> #include <boost/bind.hpp> #include <typeinfo> #include <cxxabi.h> char* demangle(const char *demangle) { int status; return abi::__cxa_demangle(demangle, 0, 0, &status); } template <typename F,typename Args> requires std::Callable<F , Args > F::result_type mybinder(F obj,Args x) { obj(x); std::cout << demangle(typeid(F::result_type).name()) << std::endl; }; struct Functor { void operator()(std::string str){ std::cout << str << std::endl; } }; struct InvalidFunctor { typedef bool result_type; void operator()(std::string str){ std::cout << str << std::endl; std::cout << demangle(typeid(result_type).name()) << std::endl; } }; int main() { mybinder(Functor(),"Hello"); mybinder(InvalidFunctor(),"Hello"); return 0; } //出力 //$ ./a.exe //Hello //void //Hello //bool //void
こうなった
うーん。
あんまりちゃんとドラフトを読んでないからあれなんだけど、
そういえばtypedefのスコープってどうなってるんだっけ?とかいろいろとよくわからなくなってきたぞ。
もうちょっとちゃんとドラフトなりを読まないとどうにもならない気がする。