C++が使えたい 項目1~4(型推論)

サバです。最近EffectiveModernC++をやっと買えたと思ったら、ついでに期末試験が迫ってきてしまったので、現実逃避しながらその内容について書いてきたいと思います。(本の内容を自分解釈で出力しとけば忘れたときに楽かなぁ…とも思ったので!)
この本にとっかかりやすくなってもらえるとうれしいなぁ

 

<注意>
ここの内容は、サバが適当な自己解釈を重ねた結果、攪拌され白濁してしまった知識の泉から、沈殿物を素手で持ち上げようとした結果です。保証はしかねます。ご了承ください。

題名にEffectiveModernC++が入っていないのは更新が途中で止まる自信があるからです。

また、もしよろしければ、間違い、気になったところ、分かりづらいところなどを指摘してもらえると、サバがピチピチ跳ねて喜びます。喜びすぎてプログラミング言語C++第四版をあなたに投げつけるかもしれません。
ぜひ教えてください。よろしくお願いします。 < ( _ _ ) > 何でもするとは言いませんから…

 

 

 

 

1章 型推論

 

項目1 テンプレートの型推論を理解する

 

結論:大体期待通り!

C++の深淵は闇の中だとしても、templateは晴れた日のあなたの部屋くらい明るい。
C++なんて、第一に安全、第二に効率を考えて、Cから派生したってことだけ分かっていれば、もう何も怖くない!!(そう願いたい…)
まさにtemplateはそんな感じ。(希望的観測)

 

template<typename T>  と宣言したとき、Tの使い方は参照について考えると3つあります。

1. void f (T arg) { }      : arg は値渡し

  • const , volatileは完全になかったことにされる

2. void f (T& arg) { }   : arg は参照渡し

  • この関数使うときに右辺値の引数をぶち込むとエラー

3. void f (T&& arg) { } : arg はユニバーサル参照渡し

  • ユニバーサル参照 : 左辺値参照・右辺値参照のどちらもイける両刀使い

 

上記の f を f (value) と呼び出したとき、 Targ  の
推論される型を表に入れるとこうなります。大体…期待通りです。(volatileもconstと同じ)

T + &TT&T&&
TargTargTarg
int value;intintintint&int&int&
int& value;intintintint&int&int&
int&& value;intintintint&int&int&
const int value;intintconst intconst int&const int&const int&
const int& value;intintconst intconst int&const int&const int&
const int&& value;intintconst intconst int&const int&const int&
f ( 1 )   // 何か右辺値intinterrorerrorintint&&
  • Tの時、constも参照も取れるのは、値渡しでできた複製(仮引数)がどうなろうとコピー元(実引数)には関係ないからです。
  • エラーが出るのは、右辺値を書き換えても意味がなく、リテラルの参照が存在したらわけわかめになるからだとおもいます。(適当)
  • ユニバーサル参照はいろいろ変態なので覚えるのが大変そうです…

 

 

・template? 知らない子ですね

  君は、関数にを渡すだけの退屈な日々を過ごす中で、
  型も渡せたらいいのにと思ったことはあるかい?
  そういうことだよ。()

 

 

・深淵を覗きに行くために…
 (闇のコードを読むことに興味がある方は知っているべき?)

ついでにポインタ、配列、関数も入れてみましょう

T + & + αTT&T&&
TargTargTarg
const int * const value;const int *const int *const int * constconst int * const &const int * constconst int * const &
int value[4];int *int *int [4]int (&)[4]int [4]int (&)[4]
void value( int );void (*)(int)void (*)(int)void (int)void (&)(int)void (int)void (&)(int)
  • int value[4] は長さ4の int 型配列、void value( int ) は int 型引数一つを受け取る void 関数です。
  • 注目するは青文字です。値渡しでは配列や関数をポインタとして推論しています。これはC言語由来の動作で、配列や関数がポインタに成り下がる(本当はポインタじゃないけどポインタのふりをする)という動作をするためです。
  • 参照渡しはポインタのふりをする必要がないため、配列・ポインタの参照型として推論されます。

 

Tにconstつけるとこうなります。
 (VS2017でこうなるを確認できたけど理由がよくわかってなかったり…

T + const + &const Tconst T&const T&&
TargTargTarg
int value;intconst intintconst int&errorerror
int& value;intconst intintconst int&errorerror
int&& value;intconst intintconst int&errorerror
const int value;intconst intintconst int&errorerror
const int& value;intconst intintconst int&errorerror
const int&& value;intconst intintconst int&errorerror
f ( 1 )   // 何か右辺値intconst intintconst int&intconst int &&
  • const T&& は要らない子だと思ってたけど右辺値だけ受け取りたいときに使える…?
  • 右辺値と右辺値参照が頭の中でこんがらがってあぁぁぁぁ 
  • 迷ったら const 参照使ってれば一番よさそうですね

 

ポインタになるとこうなります
  ※ const が長くて表にはまらなかったのでcとしました

T + const + *T *T * constconst T *const T * const
TargTargTargTarg
int * value;   int   int*   int   int* c int c int* int c int* c
int * const value;   int   int*   int   int* c int c int* int c int* c
const int * value;c intint*c intc int* c int c int* int c int* c
const int * cosnt  value;c intc int*c intc int* c int c int* int c int* c
  • const とポインタの関係が完璧に分かっているconst教信者の方なら、const T * とか T * const とかしたときはどうなるか、すぐに予想できましたよね?(震え声)

 

ね? templateって、素直ないい子…だよ…ね?

 

 

 

 

 

 

 

項目2 autoの型推論を理解する

 

結論templateと同じ!以上!解散!


とは言えない
みたいです…
1か所だけ異なって、auto value = {1, 2, 3, 4, 5, 12}; と{}で書いたときのみ、
value は std::initializer_list<T>という型に推論されます。

左右は全く同じ意味
auto i = 1;int i = 1;
const auto ci = i;const int ci = i;
const auto d = 1.0;const double d = 1.0;
auto d2 = d;double d2 = d;
auto& ir = i;int& ir = i;
const auto& cir = irconst int& cir = ir;
auto& cir2 = cir;const int& cir2 = cir;
auto hentai = {1};std::initializer_list<int> hentai = {1};
auto hentai = {1, 1.0};error (intなのかdoubleなのか、少しでも曖昧なため)

 

 

・auto? 知らない子ですね 

  C++にはイテレータというtemplateクラス版ポインタがあるんだけど、
  そいつが指す変数の「」を取得するためにこんなコードは書きたくないよね?

  template<typename Iterator>
  void PrintValue(Iterator iterator)
  {
      std::iterator_traits<iterator>::value_type value = *iterator;
      std::cout << value;
  }

  大丈夫。こんなの覚えなくていい。そう、autoならね。

  template<typename Iterator>
  void PrintValue(Iterator iterator)
  {
      auto value = *iterator;
      std::cout << value;
  }

 

 

・深淵を覗きに行くために…

ラムダ式の引数や関数の戻り値など、関数の定義等にもautoを使えますが、
これはtemplateの型推論になるらしいです。

    auto lambda = [](auto arg){ };
    lambda({1, 3, 4});   // error std::initializer_list<int>と推論できない

    auto plsGiveMe_initializer_list()
    {
        return {1, 2, 3};    // error std::initializer_list<int>と推論できない
    }

また、std::initialzier_list<>の型推論には以下のようにC++のバージョンに左右される面があります。(デフォルトのVisualStudio2017は、C++17以降の方の動作をします。)

ソースコード推論結果
C++14以前C++17以降
auto value = {0, 1, 2};std::initializer_list<int> value = {0, 1, 2};std::initializer_list<int> value= {0, 1, 2};
auto value = {0};std::initializer_list<int> value = {0};std::initializer_list<int> value = {0};
auto value{0, 1, 2};std::initializer_list<int> value = {0, 1, 2};error
auto value{0};std::initializer_list<int> value = {0};int value = 0;

 

 

 

 

 

 

 

 

項目3 decltypeを理解する

 

結論 : そのまんま型が返ってくる!(大体そのまんま)

 

decltypeにも例外的な動作が一つあります。
単純に名前ではない左辺値式をあたえたときは
参照がもれなく付いてくるというものです。

int n;                         // 左辺値式 n
decltype(n) → int      // 単純に名前だけの左辺値式
decltype((n)) → int& // この場合は()が付いたため複雑な左辺値式と判定されるらしい…

 

項目2の変数と次の関数を使います。   void kansu(int, double){}  

左右は全く同じ意味
decltype(i)int
decltype(cir2)const int&
decltype(hentai)std::initializer_list<int>
decltype(kansu(0, 0))void
decltype(kansu)void ( int, double )
decltype((i))int&      // 唯一の例外
decltype((i + 1 + 0.1))double  // 右辺値式は例外に含まれない

 

 

・decltype? 知らない子ですね

知らなくても大丈夫。ゴリゴリtemplate書くようになるまで必要になることはないから。
それに、必要になったらいやでも覚えなきゃいけないから。(未来の自分がそう言ってる気がする)

 

・深淵を覗きに行くために…

decltypeは、autoの用に変数の型・関数の戻り値を推論するのに使えます。書式は以下の通りです。
ただし推論の方法はdecltypeのものであり、auto や template とは異なります。

左右は全く同じ意味
decltype(auto) value = 0;
decltype(auto) func()
{
    return 0;
}
int value = 0;          // 0 はint型
int func()                 // 0 はint型
{
    return 0;   
}
const int n = 0;
decltype(auto) value = n;
decltype(auto) func()
{
    return n;
}
cont int n = 0;
const int value = n;  //templateやautoはintと推論
const int func()        //templateやautoはintと推論
{
    return n;
}
int n = 0;
decltype(auto)
value = (n); // 複雑な左辺値式
decltype(auto) func()
{
    return (n);     // 複雑な左辺値式
}
int n = 0;
int&
value = n;          // 参照が付きます
int& ILoveDecltype() // 参照が付きます
{
    return n;
}
decltype(auto) value = {1, 2, 3};
decltype(auto) func()
{
    return {1, 2, 3}; // std::initializer_list<int> ?
}
error
(autoを使いますが、型の推論方法はdecltypeのものです。そのためstd::initializer_list<int>とは推論できません。)

 

 

 

 

 

 

 

項目4 推論された型を確認する

 

結論 : IDEに頼ろう!

IDE(VisualStudioとかとかとか)で気になるトークンにマウスオーバー。
これが一番(個人比)
Boost.TypeIndexというBoostライブラリも紹介されていました。
でも変な型が出る可能性は十分あるという…

そのほかにはコンパイルエラーで確認する方法、場合によっては見づらい・不正確な型名を表示する可能性がある方法なども紹介されていました。

 

 

 

 

 

 

 

< 前の項目へ   次の項目へ >

ICPC2018国内予選参加記 Tech-ONC

こんばんえるえる〜〜
毎度ガナリヤです!
部室行かなくてすいません!活動はしてるんです!集中力の問題で家でやってます・・・(お兄さん許して・・・)
今日は、ICPC国内予選に参加したので、来年のリベンジのために参加記を残しておこうとおもいます。
 
 

チーム Tech-ONC

チームTech-ONCはTNP部員かつ、同じ競技プログラミングバイト先の三人で構成されています。
思考力・実装力担当の我らが部長!こしょう!
C++の魔法使い!サバ!
手広無力おじさん!ガナリヤ!
の三人です。
このチームの特徴として

  • 普段アルバイトで、競技プログラミング問題制作を行っている
  • 得意なことがそれぞれ違い、バランスが取れている
  • 三人共コーディングを担当できる
  • いい意味でも悪い意味でも、そこまで競技プログラミングコーディング力が離れていない(他チームは、一人だけ無茶苦茶強いところが多かったりする)

という点があります。
僕が誘わせていただき、引き受けていただきて本当に嬉しかったです!


参加記

事前準備

統計の授業を三人で抜け出してご飯を食べる。
チョコレートがおいしかった。
その後、コーディング場に移動し、それぞれマクロや本の準備をしていた。
その間、サバがVisualStudio設定RTAしてて面白かったです、来年は僕も設定できるようになりたい

16:10頃、監督の先生が到着(本当に引き受けていただきありがとうございます!)
雑談をしつつ、そわそわしながら開始を待つ。
  

ICPC予選開始

16:30から予選開始。
サバニキが印刷をぶん回しつつ、僕がA問題の紙を読み、こしょうがB問題の紙を読み始めました。
その間、サバがVisualStudioの設定やマクロの準備をしており、縁の下の力持ちでした!(またnext_combinationの実装もしてもらってしまった・・・来年までにはもっと簡単な実装にします・・・)サバありがとう!

A問題 所得格差は、N人の合計の平均を取って、平均より小さい人数を数えるだけの問題で非常に簡単でやるだけでした。年々A問題は簡単になっている気がする・・・
ハラハラしつつ、doubleのガバに気をつけてAC

  

B問題とC問題

A問題を解いた後、こしょうがB問題へ。
僕とサバでC問題に移動。

こしょうにきが解いている間、C問題を二人で話し合う。
C問題 みなとハルカス

C問題は、整数bが与えられ、1~Nまでの中で連続した整数の和がbに一致する中で、最長のものを出力せよという問題でした。
二人で話し合った結果、部分累積和で10^8までぶん回すか、しゃくとり法で実装するかの二択になりました。
ここで壮大なミス。
絶対に部分累積和じゃ通らないのに、部分累積和の案を採用。ふたりともしゃくとり法はあまりお得意なお気持ちではないため、部分累積和に逃げてしまった・・・、このミスが無ければDの実装が間に合ったかもしれない(申し訳ない)

間違った部分累積和の実装が行けそうになったとき、こしょうの実装でエラーが発生していた模様。
コードを印刷してもらって、エディタを空けてもらって入れ替わりで僕が嘘解法を実装。
ちなみに、今回のB問題はCよりずっと難しかったようで、さらっと解いてるこしょうやっぱやばい。

嘘解法のCを実装した僕
「できた!天才か〜〜〜?」←嘘解法で喜んでいるクズの図
10^9の実装のため、テストケースだけ通して、もう一度こしょうと交代。
こしょうがB問題の添字ミスと、問題文の勘違いに気づいたらしく難なくAC。こしょうにきは、問題文読み間違い以外基本的にすべて通している(すごい(すごい))

その後、こしょうに変わってもらって、投げていたテストケースを見ると、数が合っておらず、嘘解法であったことに気づく(当たり前だよなぁ?)
急いで、しゃくとり法で実装してAC。やっぱ最初からしゃくとり法にするべきでした、チキっちゃだめやね・・・
  

D問題

ここから毎回僕たちが苦戦しているD問題以降パートへ。

サバニキがD問題とE問題に目をつけていて、問題概要や、解き方のコツを教えてもらった。
D問題 全チームによるプレーオフは、サッカーの試合をするとき、すべてのチームの勝敗が一致し、全員で仲良しになる総数は何個あるか?という問題だった。

僕とサバでDはやべえな・・・となり、他の問題をいろいろ見ていた。よくよく考えるとこれが悪手だった気がする。最初からずっとDのみに取り組んでいれば、時間が間に合ったかもしれない(悔しい・・・)

その後、こしょうもDに参戦し、こしょうとサバが解き方の方針を教えてくれた。(5人なら、必ず全てのチームが2勝2敗とか、普段の自分一人のコーディングなら絶対に気づけてない・・・)

その後しばらく三人でDにうなり続け、うだうだ言いながら、意見を出しつつ、next_combinationで1と0の組み合わせを求めれば計算量はあまり増えないということに気づく(正直合ってるか分からん)
よくわからないまま、時間が無いため、僕が突発的に考えた多分正攻法ではない、汚いアイディアを実装することに。
僕とこしょうでペアプロしつつ、サバがnext_combinationの実装をしてくれた(来年までにはもっと分かりやすいライブラリつくる許して・・・)
こしょうとサバのこしょうを借りつつ、ガバガバ実装を行い、時間と戦いつつ、コーディングが完成。
奇跡に祈りつつ、実行したところ、添字エラー。添字エラーを解消している中で、時間になってしまった・・・

  


振り返り

ICPCに参戦してみて、すんごい楽しかったです。
他の人と、問題を解くためのアイディア出し合ったり、ふざけながら問題通すのって、学生のうちしかできない気がする(仕事になると、ふざけることはできない・・・)
また、自分の実装の弱さに気付かされた回でもありました・・・。他の二人の実装がすごい綺麗で、僕のコーディングがかなり汚いので、デバッグしづらくて二人がかなり僕のコードを追うの辛かったと思います。もっと簡潔なコーディングできるよう目指していきたいです。

何はともあれ、すごい楽しかった
来年もこの三人でTech-ONCとして出るという話をした。
来年は三人共、競技プログラミングがうまくなり、僕はAtCoder年内中に青色、来年のICPCまでに黄色になっているはずなので(希望的観測)、来年のチーム名はTech-ONS(O(N^2))でしょうか・・・

何も考えず、今日あったことをそのまま書いているだけで、読みづらいと思います。すいません。
それぐらい楽しかったです
明日はSoundHound頑張ろうと思います!

ガナリヤでした!