汎整数定数式は C++ の多くの場面で用いられる。配列のサイズや、 bit-field (※訳語データベースへ?) 、列挙値の初期化や、型でないテンプ レートパラメータ (※訳語データベースへ?) の引数として。しかしながら多 くのコンパイラは汎整数定数式の扱いに問題を抱えている。つまりこの結果と して、特に型でないテンプレートパラメータを使ったプログラミングは、困難 に満ちたものになりうる。そしてしばしば、特定のコンパイラでは型でないテ ンプレートパラメータはサポートされていない、という間違った推論に陥いら せる。この短い記事は、これに従えば、汎整数定数式を Boost に正しくサポー トされている全てのコンパイラでポータブルな作法で用いることができるよう になるガイドラインと回避方法を提供するようにデザインされている。この記 事は主に Boost ライブラリの作者に向けられたものであるが、何故 Boost の コードがそのような方法で書かれているのかを理解することや、自身でポータ ブルなコードを書くことを欲するユーザにとっても役に立つものであろう。
汎整数定数式は標準のセクション 5.19 で述べられている。そしてしばし ば「コンパイル時定数」と呼ばれる。汎整数定数式は下記のいずれかになりう る:
const int my_INTEGRAL_CONSTANT = 3;
struct myclass
{ static const int value = 0; };
struct myclass
{ enum{ value = 0 }; };
sizeof
式の結果、例えば:sizeof(foo(a, b, c))
static_cast
の結果。INTEGRAL_CONSTANT1 op INTEGRAL_CONSTANT2
op INTEGRAL_CONSTANT1
以下のガイドラインは特別な順番で並んでいるわけではない (言い換えれ ば、申し訳無いが、あなたはこれら全てに従う必要があるということだ)。そ して不完全でもあるかもしれない、コンパイラの変更やさらなる問題との遭遇 のために、さらにガイドラインが加わるかもしれない。
クラスメンバの定数を宣言するときは必ず BOOST_STATIC_CONSTANT マクロを使う。
template <class T> struct myclass { BOOST_STATIC_CONSTANT(int, value = sizeof(T)); };
Rationale: メンバ定数のインライン初期化をサポートしていないコンパイ ラもある。メンバの列挙をうまく扱えないコンパイラもある (それらは必ずし も汎整数定数式として扱わない)。BOOST_STATIC_CONSTANT マクロは問題のコ ンパイラで最も適切な方法を使用する。
int より大きな型の汎整数定数式を宣言しない。
Rationale: 理論上は全ての汎整数型を汎整数定数式の中で使用できるが、 実際問題として、大くのコンパイラは汎整数定数式を int より大きく ない型に制限する。
論理演算子を汎整数定数式に対して使わない。代わりにテンプレー トメタプログラミングを使う。
<boost/type_traits/ice.hpp> ヘッダはたくさんの回避方法のテン プレートを含んでいる。それは論理演算子の役割りを成し遂げる。例えば以下 のように書く代わりに:
INTEGRAL_CONSTANT1 || INTEGRAL_CONSTANT2
以下を使いなさい:
::boost::type_traits::ice_or<INTEGRAL_CONSTANT1,INTEGRAL_CONSTANT2>::value
Rationale: 多くのコンパイラ)(特に Borland と Microsoft のコンパイラ) は論理演算子を含む汎整数定数式を真の汎整数定数式として認識しない傾向が ある。この問題は通常、汎整数定数式がテンプレートのコードの内部の奥深く にあって、複写して診断することが難しい場合にのみ現れる。
型でないテンプレート引数として使われる汎整数定数式の中でいかなる 演算子も使うな。
以下よりも:
typedef myclass<INTEGRAL_CONSTANT1 ==
INTEGRAL_CONSTANT2> mytypedef;
以下を使いなさい:
typedef myclass< some_symbol> mytypedef;
ただし、some_symbol
はその値が (INTEGRAL_CONSTANT1
== INTEGRAL_CONSTANT2)
となる汎整数定数式に与えられた名前である。
Rationale: 古い EDG ベースのコンパイラ (それがそのプラットフォーム で最新のバージョンである場合もある。) は、演算子を含む式を型でないテン プレートパラメータであると認識しない。たとえそのような式が汎整数定数式 としてどこか他の場所で使うことができるとしても。
汎整数定数式を参照するために、常に完全に修飾された名前を使い なさい。
例えば:
typedef
myclass< ::boost::is_integral<some_type>::value> mytypedef;
Rationale: 少なくとも一つのコンパイラ (Borland のもの) は名前が完全 に修飾されていなければ (完全に修飾されているとは、:: で始まっているこ とを指す)、汎整数定数式の名前を認識しない。
'<' と '::' の間には常に空白を入れなさい。
例えば:
typedef
myclass< ::boost::is_integral<some_type>::value> mytypedef;
^
ここにスペースがあることを確認しなさい!
Rationale: <: はそれ自身で合法的な二重字であって、それゆえ <:: は [: と同様に解釈される。
汎整数定数式としてローカルな名前を使うな。
Example:
template <class T> struct foobar { BOOST_STATIC_CONSTANT(int, temp = computed_value); typedef myclass<temp> mytypedef; // error };
Rationale: 少なくとも一つのコンパイラ (Borland のもの) はこれを受け 入れない。
しかしながら、以下を使うことによってこれを修正することができる:
template <class T> struct foobar { BOOST_STATIC_CONSTANT(int, temp = computed_value); typedef foobar self_type; typedef myclass<(self_type::temp)> mytypedef; // OK };
これは少なくとも一つのコンパイラ (VC6) で通らない。汎整数定数式を別 の特性クラスに移す方がより良い。
template <class T> struct foobar_helper { BOOST_STATIC_CONSTANT(int, temp = computed_value); }; template <class T> struct foobar { typedef myclass< ::foobar_helper<T>::value> mytypedef; // OK };
型で無いテンプレートパラメータのために他に依存する値を使うな。
例えば:
template <class T, int I = ::boost::is_integral<T>::value> // Error can't deduce value of I in some cases. struct foobar;
Rationale: この種の使い方は Borland C++ で失敗する。これはデフォル ト値が前のテンプレートパラメータに依存している場合のみの問題であること に注意しなさい。例えば、以下は問題無い:
template <class T, int I = 3> // OK, default value is not dependent struct foobar;
以下の問題は解決していないか、コンパイラ毎の解決があるか、一つ以上 のコーディングガイドラインを破るかのどれかである。
numeric_limits に気をつけなさい
ここには三つの問題がある:
template <class T> struct limits_test { BOOST_STATIC_ASSERT(::std::numeric_limits<T>::is_specialized); };
このコードはたとえテンプレートがインスタンス化されなくても VC6 でコン
パイルに失敗する。いくつかの奇怪な理由のために
::std::numeric_limits<T>::is_specialized
はテンプレー
トパラメータ T が何であろうと常に偽と評価される。この問題は
std::numeric_limits に依存する式に限定されるようである: 例えば、もし
::std::numeric_limits<T>::is_specialized
を
::boost::is_arithmetic<T>::value
に置換すれば、全てう
まくいく。以下の回避方法もうまく働くが、コーディングガイドラインに抵触す
る:
template <class T> struct limits_test { BOOST_STATIC_CONSTANT(bool, check = ::std::numeric_limits<T>::is_specialized); BOOST_STATIC_ASSERT(check); };
だから、以下のようなものが多分最上の手段である:
template <class T> struct limits_test { #ifdef BOOST_MSVC BOOST_STATIC_CONSTANT(bool, check = ::std::numeric_limits<T>::is_specialized); BOOST_STATIC_ASSERT(check); #else BOOST_STATIC_ASSERT(::std::numeric_limits<T>::is_specialized); #endif };
sizeof 演算子の使い方に気をつけなさい。
私の知る限り、全てのコンパイラはその引数が型の名前 (やテンプレートの 識別子) である場合 sizeof 式を正しく扱うようだ。しかしながら以下のような 場合問題が起こりうる:
必要無ければ boost::is_convertible を使うな
is_convertible は sizeof 演算子を用いて実装されているので、Metroworks のコンパイラと使う場合は常に間違った値を返し、Borland のコンパイラではコ ンパイルされないかもしれない。(テンプレート引数が使われるかどうかに依る)。
Copyright Dr John Maddock 2001, all rights reserved.
Japanese Translation Copyright (C) 2003 shinichiro.h <g940455@mail.ecc.u-tokyo.ac.jp>.
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の 複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」 に提供されており、いかなる明示的、暗黙的保証も行わない。また、 いかなる目的に対しても、その利用が適していることを関知しない。