IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

C++ の型変換

http://www.kuzbass.ru:8086/docs/isocpp/special.html#class.conv

クラスの型変換はコンストラクタと型変換関数を使って定義できる。これらの型変換は、ユーザー定義型変換と呼ばれ、暗黙的型変換、変数初期化子、明示的型変換に使われる。
ユーザー定義型変換は、型変換の定義が曖昧じゃないときにだけ適用される。型変換は、アクセス制御規則(private, public, protected)に従う。アクセス制御規則は、曖昧さの解決のあと適用される。
もっとも使われるユーザー定義型変換は、単一値への暗黙的型変換だ。

    class X {
        //  ...
    public:
        operator int();
    };

    class Y {
        //  ...
    public:
        operator X();
    };

    Y a;
    int b = a;                      //  NG: X().operator int() は呼ばれない
    int c = X(a);                   //  OK: X().operator int() が呼ばれる

ユーザー定義型変換は、曖昧でないときにだけ暗黙的型変換で使われる。子クラスの型変換関数で親クラスの他の型への型変換関数を隠すことはできない。関数オーバーロード解決(すごいややこしい)によって、最適な変換関数が選択される(その結果、曖昧と判断される可能性がある)。

    class X {
    public:
        //  ...
        operator int();
    };

    class Y : public X {
    public:
        //  ...
        operator char();
    };

    void f(Y& a)
    {
        if (a) {                    //  NG: Y には int と char へのユーザー定義型変換が定義されているので、曖昧
        }
    }

コンストラクタによる型変換

explicit なしの 1 個の引数でよびだせるコンストラクタの定義は、その引数の型から、そのクラス型への型変換を定義する。このようなコンストラクタを型変換コンストラクタと呼ぶ。

    class X {
        //  ...
    public:
        X(int);
        X(const char*, int =0);
    };

    void f(X arg)
    {
        X a = 1;                    //   a   =   X(1)
        X b = "Jessie";             //   b   =   X("Jessie",0)
        a = 2;                      //   a   =   X(2)
        f(3);                       //   f(X(3))
    }

explicit 有りのコンストラクタは、変数の直接初期化や明示的キャストしか出来ない。

    class Z {
    public:
    	explicit Z();
    	explicit Z(int);
    	//  ...
    };

    Z a;                            //  OK: 直接初期化
    Z a1 = 1;                       //  NG: 暗黙的型変換
    Z a3 = Z(1);                    //  OK: 直接初期化
    Z a2(1);                        //  OK: 直接初期化
    Z* p = new Z(1);                //  OK: 直接初期化
    Z a4 = (Z)1;                    //  OK: 明示的キャスト
    Z a5 = static_cast<Z>(1);       //  OK: 明示的キャスト

コピーコンストラクタは型変換コンストラクタだ。暗黙的に定義されるコピーコンストラクタは、 explicit なコンストラクタではないので、暗黙的型変換に使う事ができる。

型変換関数

あるクラス X の以下のような名前のメンバ関数は、

    conversion-function-id:
    	operator conversion-type-id

    conversion-type-id:
    	type-specifier-seq conversion-declaratoropt

    conversion-declarator:
    	ptr-operator conversion-declaratoropt

X から conversion-type-id への型変換を定義する。このようなメンバ関数を型変換関数と呼ぶ。引数と戻り値も定義しない。型変換関数の型は、引数無しで conversion-type-id を返すメンバ関数の型となる。型変換関数は、同じ型のオブジェクト同士の変換、親クラスからの変換、 void への変換には使われない

    class X {
        //  ...
    public:
        operator int();
    };

    void f(X a)
    {
        int i = int(a);
        i = (int)a;
        i = a;
    }

関数 f 内のすべての文で型変換関数 X::operator int() による型変換が行われる
ユーザー定義型変換は、変数初期化時や代入時でしか使われないということはない。(例えば、 if や、三項演算子の条件部や、 && のオペランド

    void g(X a, X b)
    {
        int i = (a) ? 1+a : 0;
        int j = (a&&b) ? a+b : i;
        if (a) {                    //  ...
        }
    }

The conversion-type-id shall not represent a function type nor an array type. The conversion-type-id in a conversion-function-id is the longest possible sequence of conversion-declarators.
This prevents ambiguities between the declarator operator * and its expression counterparts.

    &ac.operator int*i;             //  syntax error:
                                    //  parsed as:  &(ac.operator   int   *)   i
                                    //  not as:  &(ac.operator   int)*i

The * is the pointer declarator and not the multiplication operator.
Conversion functions are inherited.
Conversion functions can be virtual.