int互換クラス

しのはらのC++実験室トップページ

組込み型をclassで実現する

 C++で扱うデータの型にはintやcharなどの組込み型と呼ばれるものと、 class,struct,union,enumなどのユーザ定義型がありますが、 それらは言語仕様によって明確に区別されています。
 しかし、C++の大きな特徴である演算子オーバーロードの機能を使用すれば、 あたかも組込み型であるかのようなユーザ定義型を実現することが、 できるのではないかと考えたわけです。 また、もし完全にはそれができないとしても、 C++の限界を知るという意味で十分意味のある試みだと思い、挑戦してみました。

int互換クラス“TInt

 目標は「intの代わりにintと同じに使えるclass型」を作ること。
 たとえば、リンクするすべての翻訳単位のソースコードの先頭に、 以下のコードを書いてもうまく動作するということです。
#include "tint.h"
#define int ::TInt
 実際はいろいろな事情によりそれほどうまくはいかないことが分かりました。
ソースコードはこちら

TIntの特記事項

■'int'との互換性の高さ
 ・あらゆる組込みスカラー型との間で相互に変換可能。
 ・printf等の可変個引数関数に対して、intとして振る舞う。
(例)
TInt x=10;
printf("%d", x);

 ・'&'演算子が'int*'型を返す。
(例)
void func(int*);
TInt x=10;
func(&x);

 ・任意のポインタに対するオフセット演算のオフセットとして使用できる。
(例)
class A;
TInt x=10;
A *p=new A[100];
p += x;

 ・iostreamクラスにおける挿入と抽出で'int'と同じ扱いになる。
 TIntに対する<<,>>演算子を定義する必要がない。
 (ただし、一部のコンパイラで問題あり)

■'int'に取って代われない場面(言語仕様上の壁)
 ・静的な配列の宣言の添字は明示的整定数を要求する。
(例)
const int i=10;
const TInt x=10;
int a[i];
int A[x];	// エラー(xは定数ではない)

 ・'int'と同じインターフェイスを持っていても'int'とは同一ではない。
(例)
TInt x = 10;
int y = 20;
bool b = true;
int z = b ? x : y;	// エラー(xとyは型が違う)

■一部のコンパイラにおける問題
 ・new[]演算子の添字。(Watcom C/C++ 11.0J)
(例)
const int i=10;
const TInt x=10;
int *b = new int[i];
int *B = new int[x];		// エラー(xは整数ではない)
int *C = new int[(int)x];	// OK(キャストで解決)

 Watcom C/C++がnew[]の添字に明示的整数を要求することが問題。

 ・iostreamクラスにおける抽出。(Borland C++ 5.02J)
 TIntに対する>>演算子を定義する必要がある。
 これは operator int&() と operator int()const を曖昧と判断する Borland C++の問題。

まとめ

 この試みは完全にはなし得ないということが分かりましたが、 それでもけっこううまく行くということも分かりました。 たぶん、C++が一から作られた言語ではなくC言語を改造して作った言語 だから、そうした不完全性が残ってしまったのだと思います。

おまけ:“TInt”の作り方

 クラスTIntをどのように作ったかを以下に紹介します。

データ
 データメンバはint型の変数一つがあるだけです。
デフォルトコンストラクタ
 何もしないコンストラクタです。
 好みに応じて値をゼロに初期化する処理を書いても、 言語仕様上intとの互換性を失うことはありません。
コピーコンストラクタ
 特記事項なし。
組込み型によるコンストラクタ
 すべての組込み型からのコンストラクタを定義します。
 ただしintより大きな型(longや浮動小数点数)からのコンストラクタには、 キーワードexplicitを使用します。
代入演算子
 すべての組込み型の代入とTIntの代入を定義します。
キャスト演算子
 キャスト演算子は以下の2つが必要かつ十分です。
 特に後者は、istreamクラスの抽出演算子の前でTIntintになりすますために重要です。
  operator int()const;
  operator int&();

 これら以外のキャスト演算子を定義することは、 ほとんどすべての演算において曖昧性を与える結果になります。
アドレス演算子
 アドレス演算子を定義できることは、C++の異常さを物語っています。
  int* operator&();
  const int* operator&()const;

 アドレス演算子を定義すると、 もはや自分自身のアドレスを得る手段はなくなってしまいます。
単項算術演算子
 operator+()constoperator-()constは、 TIntを返します。
単項ビット演算子
 operator~()constは、 TIntを返します。
単項論理演算子
 operator!()constは、 boolを返します。
インクリメント演算子とデクリメント演算子
 特記事項なし。
算術代入演算子
 operator+=,operator-=,operator*=,operator/=,operator%= の引数違い版を、 TIntおよびすべての組込み型に対して定義します。
ただし、剰余代入は整数型に対してのみ定義します。
ビットシフト代入演算子
 operator<<=,operator>>= の引数違い版を、 TIntおよびすべての組込みの整数型に対して定義します。
ビット代入演算子
 operator&=,operator|=,operator^= の引数違い版を、 TIntおよびすべての組込みの整数型に対して定義します。
二項算術演算子
 operator+,operator-,operator*,operator/,operator% (すべて二項演算子)の引数違い版を、 TIntおよびすべての組込み型に対して定義します。
ただし、剰余代入は整数型に対してのみ定義します。
戻り値型は引数2つのうち大きい方になります。
ビットシフト演算子
 operator<<,operator>> (すべて二項演算子)の引数違い版を、 TIntおよびすべての組込みの整数型に対して定義します。
戻り値型は左の引数型とTIntのうちの大きい方になります。
二項ビット演算子
 operator&,operator|,operator^ (すべて二項演算子)の引数違い版を、 TIntおよびすべての組込みの整数型に対して定義します。
戻り値型は引数2つのうち大きい方になります。
比較演算子
 operator==,operator!=,operator<=,operator>=, operator<,operator> の引数違い版を、 TIntおよびすべての組込み型に対して定義します。
ポインタに対するオフセット演算
 あらゆるポインタに対する operator+,operator-,operator+=,operator-= をテンプレート関数として定義します。