effective modern c++ 勉強会#3 item 15
TRANSCRIPT
Effective Modern C++勉強会#3Item 15
刈谷 満(@kariya_mitsuru)
2015/3/25
Item 15: Use constexpr whenever possible.
「市民、あなたは constexprですか」
「もちろんです、C++。constexprであることは市民の義務です」
© ボレロ村上
Item 15: Use constexpr whenever possible.
constexpr -混乱しやすい新語オブザC++11
最高金賞受賞!!!
constに毛の生えたようなの、と思っていませんか?
オブジェクトに使えばだいたい合ってる。
⇓
単なる定数じゃなくてコンパイル時に分かる定数
Item 15: Use constexpr whenever possible.
constexpr -混乱しやすい新語オブザC++11
最高金賞受賞!!!
constに毛の生えたようなの、と思っていませんか?
でも関数に使うと…
⇓
関数の結果が定数なわけでもましてやコンパイル時定数とも限らない。
Item 15: Use constexpr whenever possible.
でもまずは、
constexprオブジェクトから。
Item 15: Use constexpr whenever possible.
constexprオブジェクトとは
1. constオブジェクト
2. だけじゃない!コンパイル時に値が分かるオブジェクト!
ホントはコンパイル時じゃなくてリンク時でいい時もあるんだけど…
Item 15: Use constexpr whenever possible.
constexprオブジェクトの特徴
• ROMに置ける。⇒特に組み込みシステムでは重要な機能
• "integral constant expression" が要求される箇所で使用することができる。⇒例えば、配列サイズ、整数テンプレート実引数(std::array の長さとか)、列挙子の値、アラインメント指定、などなど。
Item 15: Use constexpr whenever possible.int sz;
…
constexpr auto arraySize1 = sz;
std::array<int, sz> data1;
constexpr auto arraySize2 = 10;
std::array<int, arraySize2> data2;
エラー!sz の値は
コンパイル時には分からない
OK!10 はコンパイル時定数
OK!arraySize2 は constexpr
非 constexpr 変数
Item 15: Use constexpr whenever possible.
一応注意しておくと、
単なる const じゃダメだよ。
Item 15: Use constexpr whenever possible.
int sz;
…
const auto arraySize = sz;
std::array<int, arraySize> data;
非 constexpr 変数(前と一緒)
OK!arraySize は
sz の const コピー
エラー!arraySize の値は
コンパイル時には分からない
Item 15: Use constexpr whenever possible.
簡単にいえば、constexpr オブジェクトは const だけど、
逆は必ずしも真では無い。
もしコンパイル時定数が欲しければ、const じゃなくて constexpr を使う事。
Item 15: Use constexpr whenever possible.
簡単にいえば、constexpr オブジェクトは const だけど、
逆は必ずしも真では無い。
もしコンパイル時定数が欲しければ、const じゃなくて constexpr を使う事。
(余談)あれ?今まで const 変数で配列のサイズとかテンプレート引数とか
指定してきたような~?
だけどOK!でも何で?だからOK!
Item 15: Use constexpr whenever possible.
const int arraySize = 10;
std::array<int, arraySize> data;
constexpr 変数じゃないconstexpr 変数じゃないけどコンパイル時定数!
Item 15: Use constexpr whenever possible.
簡単にいえば、constexpr オブジェクトは const だけど、
逆は必ずしも真では無い。
もしコンパイル時定数が欲しければであることを保証したければ、const じゃなくて constexpr を使う事。
Item 15: Use constexpr whenever possible.
(余談)ちなみに…
constexpr なオブジェクトは初期化無しでは宣言できません!
constexpr int var; エラー!初期化子がない!
Item 15: Use constexpr whenever possible.
(余談)ちなみに…
constexpr な静的オブジェクトは普通の const なオブジェクトと一緒でデフォルト内部リンケージ!
だから、ヘッダで定義しておくと実体がいっぱいできたりする。
(事がある。ODR-used の関係で実体が出来ないことも多いけど…)
だからと言って、ヘッダファイルで extern 指定して外部リンケージにすると、リンク時にぶつかる…
Item 15: Use constexpr whenever possible.
さてお待ちかね、constexpr な関数です。
constexpr 関数に、コンパイル時定数だけを与えると…
⇒コンパイル時定数!コンパイル時定数が必要な所で使える!
constexpr 関数に、コンパイル時定数以外も与えると…
⇒普通(コンパイル時定数ではない)コンパイル時定数が必要な所で使うとエラー!
Item 15: Use constexpr whenever possible.
さてお待ちかね、constexpr な関数です。
constexpr 関数に、コンパイル時定数だけを与えると…
⇒コンパイル時定数!コンパイル時定数が必要な所で使える!
constexpr 関数に、コンパイル時定数以外も与えると…
⇒普通(コンパイル時定数ではない)コンパイル時定数が必要な所で使うとエラー!
(余談)…実はそうとも限らないんだけど…
Item 15: Use constexpr whenever possible.
(余談)引数がコンパイル時定数でもダメな例
constexpr int div(int numer, int denom) {return numer / denom;
}
constexpr int i1 = div(1, 0);
constexpr int lshift(int num, int bit) {return num << bit;
}
constexpr int i2 = lshift(1, -1);
エラー!0 除算はUB!
エラー!マイナスシフトはUB!
Item 15: Use constexpr whenever possible.
例えば…
様々な条件下での実験結果をstd::arrayに格納したい!
かつ
条件の種類はn種類あって、それぞれ3通りの状態がある!
⇓
std::pow(3, n) でおk?
Item 15: Use constexpr whenever possible.
例えば…
様々な条件下での実験結果をstd::arrayに格納したい!
かつ
条件の種類はn種類あって、それぞれ3通りの状態がある!
⇓
std::pow(3, n) でおk?
Item 15: Use constexpr whenever possible.
ダメな理由
その1:std::pow は浮動小数点型!
⇒std::size_t にキャストすればいんじゃね?
その2:std::pow は constexpr じゃない!
⇒つらぽよ…
Item 15: Use constexpr whenever possible.
ダメな理由
その1:std::pow は浮動小数点型!
⇒std::size_t にキャストすればいんじゃね?
その2:std::pow は constexpr じゃない!
⇒つらぽよ…
じゃあsprout::pow 使おう!!!
じゃあsprout::pow 使おう!!!
作ろう!!!
pow は constexpr 関数
Item 15: Use constexpr whenever possible.constexpr
int pow(int base, int exp) noexcept
{
…
}
こんなの作れば、std::array のサイズ指定も怖くない!
constexpr auto numConds = 5;
std::array<int, pow(3, numConds)> results;
pow は constexpr 関数ちなみに例外も吐かないよ
実装は後のお楽しみだ。クックックッ…
results の要素数は3 の numConds 乗!
Item 15: Use constexpr whenever possible.しかも、これなら実行時にもお使い頂けます!
auto base = readFromDB(“base”);
auto exp = readFromDB(“exponent”);
auto baseToExp = pow(base, exp);
値を実行時に取得すると…
pow 関数も実行時に呼び出し!
Item 15: Use constexpr whenever possible.しかも、これなら実行時にもお使い頂けます!
auto base = readFromDB(“base”);
auto exp = readFromDB(“exponent”);
auto baseToExp = pow(base, exp);
値を実行時に取得すると…
pow 関数も実行時に呼び出し!
TMPとは違うのだよ、
TMPとは
© ランバ・ラル
Item 15: Use constexpr whenever possible.
で、どうやって書くの?
C++11 だと…
関数本体に書ける実行文は return 文 1 つだけ!
え~と…でも、まあ、
if 文使えなくても条件演算子 ?: があるし、
ループ書けなくても再帰があるし、
return 文 1 つだけでも関係ないよね!
Item 15: Use constexpr whenever possible.
constexpr int pow(int base, int exp) noexcept
{
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
このくらいだったら、まぁ何とか読み書きできるけど、ちょっと複雑になったら辛いなぁ…
Item 15: Use constexpr whenever possible.ちなみに、C++14 なら…
constexpr int pow(int base, int exp) noexcept // C++14
{
auto result = 1;
for (int i = 0; i < exp; ++i)
result *= base;
return result;
}
見える、見えるぞ…
私にも、コードが見える!
Item 15: Use constexpr whenever possible.
実はいろいろ制約あり
引数と戻り値の型は「リテラル型」じゃないとダメなんです。
リテラル型って何ぞ?
平たく言うと、コンパイル時定数になれる型のこと。
Item 15: Use constexpr whenever possible.
リテラル型
C++11 では、組み込み型は void を除いて全部OK!
ユーザ定義型も一定の条件を満たせばOK!
だってコンストラクタやメンバ関数も constexpr になれるから!
Item 15: Use constexpr whenever possible.
リテラル型
C++11 では、組み込み型は void を除いて全部OK!
ユーザ定義型も一定の条件を満たせばOK!
だってコンストラクタやメンバ関数も constexpr になれるから!
…どゆこと?
class Point {public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept: x(xVal), y(yVal) {}
constexpr double xValue() const noexcept { return x; }constexpr double yValue() const noexcept { return y; }
void setX(double newX) noexcept { x = newX; }void setY(double newY) noexcept { y = newY; }
private:double x, y;
};
Item 15: Use constexpr whenever possible.
class Point {public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept: x(xVal), y(yVal) {}
constexpr double xValue() const noexcept { return x; }constexpr double yValue() const noexcept { return y; }
void setX(double newX) noexcept { x = newX; }void setY(double newY) noexcept { y = newY; }
private:double x, y;
};
Item 15: Use constexpr whenever possible.
constexpr コンストラクタにコンパイル時定数を渡すと…
全てのメンバ変数がコンパイル時定数になる!
class Point {public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept: x(xVal), y(yVal) {}
constexpr double xValue() const noexcept { return x; }constexpr double yValue() const noexcept { return y; }
void setX(double newX) noexcept { x = newX; }void setY(double newY) noexcept { y = newY; }
private:double x, y;
};
Item 15: Use constexpr whenever possible.
constexpr コンストラクタにコンパイル時定数を渡すと…
全てのメンバ変数がコンパイル時定数になる!
オブジェクト自体もコンパイル時定数に
なる!
Item 15: Use constexpr whenever possible.constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);
constexpr
Point midpoint(const Point& p1, const Point& p2) noexcept
{
return { (p1.xValue() + p2.xValue()) / 2,
(p1.yValue() + p2.yValue()) / 2 };
}
constexpr auto mid = midpoint(p1, p2);constexpr オブジェクトを
constexpr 関数の結果で初期化!コンパイル時定数!!!
constexprメンバ関数を呼ぶ!
constexpr コンストラクタはコンパイル時に走る!コンパイル時定数!!!
Item 15: Use constexpr whenever possible.constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);
constexpr
Point midpoint(const Point& p1, const Point& p2) noexcept
{
return { (p1.xValue() + p2.xValue()) / 2,
(p1.yValue() + p2.yValue()) / 2 };
}
constexpr auto mid = midpoint(p1, p2);constexpr オブジェクトを
constexpr 関数の結果で初期化!コンパイル時定数!!!
constexprメンバ関数を呼ぶ!
constexpr コンストラクタはコンパイル時に走る!コンパイル時定数!!!
素晴らしい!
Item 15: Use constexpr whenever possible.
今までコンパイル時 ------ 超えられない壁 ------ 実行時
今境界は曖昧に…
Item 15: Use constexpr whenever possible.
今までコンパイル時 ------ 超えられない壁 ------ 実行時
今境界は曖昧に…
実行時処理をコンパイル時処理にマイグレーションすれば、
実行速度はより速くなる!!!
Item 15: Use constexpr whenever possible.
今までコンパイル時 ------ 超えられない壁 ------ 実行時
今境界は曖昧に…
実行時処理をコンパイル時処理にマイグレーションすれば、
実行速度はより速くなる!!!でもコンパイル時間は長く…
Item 15: Use constexpr whenever possible.
今までコンパイル時 ------ 超えられない壁 ------ 実行時
今境界は曖昧に…
実行時処理をコンパイル時処理にマイグレーションすれば、
実行速度はより速くなる!!!でもコンパイル時間は長く…
Item 15: Use constexpr whenever possible.
今までコンパイル時 ------ 超えられない壁 ------ 実行時
今境界は曖昧に…
実行時処理をコンパイル時処理にマイグレーションすれば、
実行速度はより速くなる!!!でもコンパイル時間は長く…
Item 15: Use constexpr whenever possible.
(余談)ちなみに…
constexpr コンストラクタの使い道はそれだけじゃない!
静的オブジェクトの初期化順の問題の一部を解決できる!(@cpp_akira さんありがとう!!!)
詳しくは Webで!http://cpprefjp.github.io/reference/mutex/mutex/op_constructor.html
(後でちょっとだけ出てくるよ!)
Item 15: Use constexpr whenever possible.
ところで…
setX と setY は constexpr じゃないけど、
オブジェクトの値変更するんだから仕方ないよね…
Item 15: Use constexpr whenever possible.
そんなメンバ関数も、constexpr になれる。
そう、
C++14 ならね。
Item 15: Use constexpr whenever possible.class Point {
public:
…
constexpr void setX(double newX) noexcept // C++14
{ x = newX; }
constexpr void setY(double newY) noexcept // C++14
{ y = newY; }
…
};
Item 15: Use constexpr whenever possible.
C++11 で constexpr に出来なかった理由は…
その1:constexpr メンバ関数は暗黙で const 修飾!
だから、オブジェクトの状態は変更できない!
⇒C++14 からは暗黙の const 修飾は亡くなりました!
その2:戻り値の型 void はリテラル型じゃない!
⇒C++14では void もめでたくリテラル型になりました
Item 15: Use constexpr whenever possible.
(余談)Caution!constexpr メンバ関数の暗黙 const 修飾は C++14 で亡くなるので、たとえ C++11 で書いていても constexpr メンバ関数には明示的にconst 修飾しておきましょう!!!
そうしないと、コンパイラが C++14 に移行した時に挙動が変わってしまいます!!!
(clang は 3.3 から警告が出ます)
Item 15: Use constexpr whenever possible.
(余談)ちなみに…
constexpr な関数は暗黙で inline です!
(実際、極限のインライン化ですよね…)
だから、普通ヘッダで定義しておく。
(inline だから重複定義にならない)
あと、constexpr として使うには宣言だけじゃなく定義が必要!
Item 15: Use constexpr whenever possible.
でも…
セッタ関数 constexpr で何が嬉しいの?
Item 15: Use constexpr whenever possible.こんな関数も書けるよ!
// 点 p の原点に対する鏡映を返す(C++14)
constexpr Point reflection(const Point& p) noexcept
{
Point result;
result.setX(-p.xValue());
result.setY(-p.yValue());
return result;
}
非 const の Point を作る
x と y を設定する
それのコピーを返す
Item 15: Use constexpr whenever possible.当然こんなこともできるよ!
constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);
constexpr auto mid = midpoint(p1, p2);
constexpr auto reflectedMid =
reflection(mid);
reflectedMid の値は(-19.1, -16.5) で、コンパイル時に分かる
このへんは前と一緒
Item 15: Use constexpr whenever possible.
さて、本項目のアドバイスの理由は明らかになったでしょ?
1. constexpr オブジェクトと constexpr 関数は、非 constexpr
のものよりもより多くの文脈で使える。
2. constexpr を使う事で、あなたが作ったオブジェクトや関数の
使用可能なシチュエーションを最大化することができる。
Item 15: Use constexpr whenever possible.
Caution!!!constexpr はオブジェクトや関数の
インタフェースの一部
だ!
constexpr、それは
"定数式が必要な箇所で使う事が出来る"
という宣言
Item 15: Use constexpr whenever possible.
Caution!!!
constexpr なオブジェクトや関数を提供
⇓
クライアントコード内の定数式が必要な箇所で使われる
⇓
その後、constexpr を削除
⇓
クライアントコードのコンパイルエラーが大量発生!!!
Item 15: Use constexpr whenever possible.
Caution!!!
デバッグやチューニングのために I/O を追加しよ
⇓
一般的に I/O は constexpr 関数内では許されていない
⇓
constexpr 取らなきゃ!!!
Item 15: Use constexpr whenever possible.
Caution!!!
"whenever possible" は、
長期スパンで考えること!
Item 15: Use constexpr whenever possible.
覚えておくこと
• constexpr オブジェクトは、const で、コンパイル中に分かる値で初期化される。
• constexpr 関数は、コンパイル時に分かる値を引数に呼び出すと、コンパイル時に結果を生成する。
• constexpr オブジェクトと関数は、非 constexpr オブジェクトと関数よりも、より幅広い文脈で使用することができる。
• constexpr はオブジェクトと関数のインタフェースの一部である。
Item 15: Use constexpr whenever possible.
さあ、今日からあなたもconstexpr で
コンパイル時レイトレ!!!