この記事について
クラスの静的メンバ変数に constexpr
があると困ることがあるよ、という話と、じゃあどうすればいいかという話。
実際に困るパターン
まあまずはソースコード。
// -std=c++11 -Wall
#include <iostream>
template <typename type> void foo(type const &v) {
std::cout << v << std::endl;
}
template <typename type>
struct bar
{
constexpr static type value = 123;
};
int main() {
foo(bar<int>::value); // Undefined symbols(略) "bar::value"
return 0;
}
これがエラーになる。
リンクエラー。
エラーになるのは、bar<int>::value
の定義がないから。
bar
は template なので、bar<int>::value
の定義なんて書いてられない。困る。
クラスのメンバではない
constexpr static int out_of_class_constant = 123;
は、宣言が定義を兼ねるのでエラーにならない(対比のために static
と書いたが、 static
は不要)が、クラススコープにある
constexpr static type value = 123;
は、宣言なので、別途定義が必要になるかもしれない。
「かもしれない」と書いたのは。
コンパイラが「値がわかればいいや」と思っていれば定義がなくても困らないのでコンパイル→リンクできる。コンパイラが「アドレスほしいなぁ」と思ったら定義を探しに行き、定義がないとリンクエラーになる。つまり、コンパイラの気持ち次第。
上記のソースコードの場合、const
参照を引数としている関数に渡しているだけなのでアドレスなんて必要そうにないけど、なぜだか g++-9
も clang
もアドレスを取りに行くらしく、エラーになる。
コンパイラ様のお気持ちなので、ちょっとした変更でエラーじゃなくなる。
実際、g++-9
ではコンパイラオプション -O3
をつけるだけでビルドが通ったりする(clang++
では依然として通らないんだけど)。
邪悪な対策
アドレスを取りに行くのが原因なので、上記のコードの main
関数を以下のように変えるとビルドが通る。
int main() {
foo(bar<int>::value + 0); // 無駄に0を足すとアドレスを取らなくなる
return 0;
}
偶然なのか必然なのかはよくわからないが、手元のコンパイラではそうなる。
とはいえ、こんな対策いやだよね。
邪悪じゃない対策(とりあえず)
値のアドレスを取られなければいいので、関数にすれば良い。
こんな感じ。
// -std=c++11 -Wall
#include <iostream>
template <typename type> void foo(type const &v) {
std::cout << v << std::endl;
}
template <typename type> struct bar {
constexpr static type value() { return 123; }
};
int main() {
foo(bar<int>::value());
return 0;
}
整数なら enum
にするという手もあるけど、そうできないこともある。
関数になんかしたくないんだけど、関数にするしか手がない。
邪悪じゃない対策(C++17)
と思っていたら新しい言語機能が。
// -std=c++17 -Wall
#include <iostream>
template <typename type> void foo(type const &v) {
std::cout << v << std::endl;
}
template <typename type>
struct bar
{
inline constexpr static type value = 123; // inline 変数
};
int main() {
foo(bar<int>::value);
return 0;
}
という具合。
inline
変数にすると、クラスの外にある変数と同じように振る舞う。
なお。 inline
変数が使える C++17 以降では
constexpr
な静的データメンバは暗黙にinline
というスペシャルルールが加わったので、上記のソースに書いた inline
は不要。C++17 としてコンパイルするだけでよい。
というわけで
というわけで、C++17 以降で仕事したいね。
環境
動作確認に使った環境は以下の通り:
OS: macOS Mojave 10.14.5
clang: Apple LLVM version 10.0.1 (clang-1001.0.46.4)
gcc: g++-9 (Homebrew GCC 9.1.0) 9.1.0