1. ホーム
  2. c++

[解決済み] C++で変換演算子はどのように動作するのですか?

2022-03-08 23:02:50

質問

この簡単な例を考えてみましょう。

template <class Type>
class smartref {
public:
    smartref() : data(new Type) { }
    operator Type&(){ return *data; }
private:
    Type* data;
};

class person {
public:
    void think() { std::cout << "I am thinking"; }
};

int main() {
    smartref<person> p;
    p.think(); // why does not the compiler try substituting Type&?
}

C++では変換演算子はどのように動作するのですか?(すなわち) コンパイラはいつ変換演算子の後に定義された型を代入しようとするのでしょうか?

どのように解決するのですか?

変換関数を使用する場合と使用しない場合のランダムな状況を以下に示します。

まず、変換関数は同じクラス型や基底クラス型への変換には決して使用されないことに注意してください。

引数渡し時の変換

引数渡し時の変換は、コピー初期化のルールが適用されます。これらのルールは、参照に変換するかどうかに関係なく、あらゆる変換関数を考慮するだけです。

struct B { };
struct A {
  operator B() { return B(); }
};
void f(B);
int main() { f(A()); } // called!

引数の受け渡しは、コピー初期化の1つのコンテキストに過ぎません。もうひとつは、コピー初期化構文を使った "pure"形式です。

B b = A(); // called!

リファレンスへの変換

条件演算子では、変換先の型がlvalueの場合、参照型への変換が可能です。

struct B { };
struct A {
  operator B&() { static B b; return b; }
};

int main() { B b; 0 ? b : A(); } // called!

もう一つの参照への変換は、参照を直接、バインドする場合です。

struct B { };
struct A { 
  operator B&() { static B b; return b; }
};

B &b = A(); // called!

関数ポインタへの変換

関数ポインタや参照への変換関数を持っていて、呼び出しが行われたときに、それが使われるかもしれません。

typedef void (*fPtr)(int);

void foo(int a);
struct test {
  operator fPtr() { return foo; }
};

int main() {
  test t; t(10); // called!
}

これ、実は結構便利になる時があるんですよ。

非クラス型への変換

常にどこでも起こる暗黙の変換は、ユーザー定義の変換も使うことができます。あなたは,ブール値を返す変換関数を定義することができます。

struct test {
  operator bool() { return true; }
};

int main() {
  test t;
  if(t) { ... }
}

(この場合のboolへの変換は、より安全にするために safe-boolイディオム 他の整数型への変換を禁止するため) 変換は,組み込み演算子が特定の型を期待するところではどこでも行われます。しかし、変換は邪魔になるかもしれません。

struct test {
  void operator[](unsigned int) { }
  operator char *() { static char c; return &c; }
};

int main() {
  test t; t[0]; // ambiguous
}

// (t).operator[] (unsigned int) : member
// operator[](T *, std::ptrdiff_t) : built-in

この呼び出しは曖昧になる可能性があります。なぜなら、メンバーについては、2番目のパラメータに変換が必要で、組み込みオペレータについては、1番目のパラメータにユーザ定義の変換が必要だからです。他の2つのパラメータはそれぞれ完全に一致します。場合によっては、呼び出しがあいまいでないこともあります ( ptrdiff_t とは異なる必要があります。 int では)。

変換関数テンプレート

テンプレートはいくつかの素晴らしいことを可能にしますが、それについては非常に慎重であるべきです。以下は、ある型を任意のポインタ型に変換できるようにするものです(メンバポインタは"ポインタ型"とは見なされません)。

struct test {
  template<typename T>
  operator T*() { return 0; }
};

void *pv = test();
bool *pb = test();