サーチ…


備考

過負荷の解決はいくつかの異なる状況で起こります

  • 名前付きオーバーロードされた関数を呼び出します。候補者は、名前検索によって見つかったすべての関数です。
  • クラスオブジェクトを呼び出します。候補は通常、クラスのすべてのオーバーロードされた関数呼び出し演算子です。
  • オペレータの使用。候補は、名前空間のスコープでオーバーロードされた演算子関数、左クラスオブジェクト(存在する場合)のオーバーロードされた演算子関数、および組み込み演算子です。
  • 初期化のために呼び出す正しい変換演算子関数またはコンストラクタを見つけるためのオーバーロードの解決
    • 非リスト直接初期化( Class c(value) )の場合、候補はClassコンストラクタです。
    • 非リストコピー初期化( Class c = value )と、ユーザー定義の変換シーケンスで呼び出すユーザー定義の変換関数を見つけるため。候補はClassのコンストラクタであり、ソースがクラスオブジェクトの場合、その変換演算子が機能します。
    • クラスオブジェクトから非クラスを初期化する( Nonclass c = classObjectクラスNonclass c = classObject )。候補は、イニシャライザオブジェクトの変換演算子関数です。
    • クラスオブジェクトに参照を初期化するために( R &r = classObject )、クラスに直接rバインドできる値を生成する変換演算子関数がある場合。候補は、そのような変換演算子関数です。
    • 非集約クラスオブジェクト( Class c{1, 2, 3} )のリスト初期化の場合、候補は過負荷解決による最初のパスの初期化子リストコンストラクタです。これで実行可能な候補が見つからない場合は、 Classのコンストラクタを候補として、2番目のパスのオーバーロード解決が行われます。

完全に一致

パラメータ・タイプに必要な変換がないオーバーロード、または完全一致と見なされるタイプ間で必要な変換のみが、コールするために他の変換を必要とするオーバーロードよりも優先されます。

void f(int x);
void f(double x);
f(42); // calls f(int)

引数が同じ型への参照にバインドされるとき、その参照はより多くのCV修飾されていても変換を必要としないと見なされます。

void f(int& x);
void f(double x);
int x = 42;
f(x); // argument type is int; exact match with int&

void g(const int& x);
void g(int x);
g(x); // ambiguous; both overloads give exact match

過負荷解決のために、 " T配列"型は " Tへのポインタ"型と正確に一致するとみなされ、関数型Tは関数ポインタ型T*と正確に一致するとみなされる。コンバージョン

void f(int* p);
void f(void* p);

void g(int* p);
void g(int (&p)[100]);

int a[100];
f(a); // calls f(int*); exact match with array-to-pointer conversion
g(a); // ambiguous; both overloads give exact match

引数のパラメータコストへの分類

オーバーロードの解決策は、引数を引数に渡すコストを、「シーケンス」と呼ばれる4つの異なるカテゴリのいずれかに分割します。各シーケンスは、ゼロ、1つまたは複数のコンバージョンを含むことができる

  • 標準的な変換シーケンス

    void f(int a); f(42);
    
  • ユーザー定義の変換シーケンス

    void f(std::string s); f("hello");
    
  • 省略記号変換シーケンス

    void f(...); f(42);
    
  • リストの初期化シーケンス

    void f(std::vector<int> v); f({1, 2, 3});
    

一般的な原則は、標準変換シーケンスが最も安価であり、その後にユーザ定義変換シーケンスが続き、その後に省略符号変換シーケンスが続くことである。

特殊なケースは、リストの初期化シーケンスです。これは変換を構成しません(初期化子リストは型を持つ式ではありません)。そのコストは、パラメータのタイプとイニシャライザリストの形式に応じて、他の3つの変換シーケンスの1つと同等になるように定義することによって決定されます。

名前の参照とアクセスのチェック

オーバーロードの解決は名前の参照後に発生ます。これは、名前の参照が失われた場合、オーバーロードの解決によってよりよく一致する関数が選択されないことを意味します。

void f(int x);
struct S {
    void f(double x);
    void g() { f(42); } // calls S::f because global f is not visible here,
                        // even though it would be a better match
};

過負荷の解決はアクセスチェックの前に行われます。アクセスできない関数は、アクセス可能な関数よりも良い一致であれば、過負荷解決によって選択することができます。

class C {
  public:
    static void f(double x);
  private:
    static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.

同様に、オーバーロードの解決は、結果のコールがexplicit整形式であるかどうかをチェックせずに発生します。

struct X {
    explicit X(int );
    X(char );
};

void foo(X );
foo({4}); // X(int) is better much, but expression is 
          // ill-formed because selected constructor is explicit

転送参照のオーバーロード

フォワーディング参照のオーバーロードがうまくいくかもしれないので、あなたは非常に注意する必要があります:

struct A {
    A() = default;           // #1
    A(A const& ) = default;  // #2

    template <class T>
    A(T&& );                 // #3
};

ここでの目的は、 Aがコピー可能であり、他のメンバーを初期化するかもしれないこの他のコンストラクタがあることです。しかしながら:

A a;     // calls #1
A b(a);  // calls #3!

建設コールには2つの実行可能なマッチングがあります:

A(A const& ); // #2
A(A& );       // #3, with T = A&

どちらも完全一致ですが、 #3#2よりもcv修飾されていないオブジェクトへの参照を取ります。したがって、より標準的な変換シーケンスがあり、実行可能な最適な関数です。

ここの解決策は、常にこれらのコンストラクタを制約することです(例えば、SFINAEを使用して)。

template <class T,
    class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
    >
A(T&& );

ここでの型特性は、 AかクラスをAから考察から公然と明白に除外することであり、これは前述の例ではこのコンストラクタを不正な形にする(したがって過負荷集合から削除する)。その結果、コピーコンストラクタが呼び出されます。これが、私たちが望むものです。

過負荷解決のステップ

過負荷解決のステップは次のとおりです。

  1. 名前の検索を介して候補関数を見つける。非適格な呼び出しは、通常の非修飾ルックアップと引数依存のルックアップ(該当する場合)の両方を実行します。

  2. 候補関数のセットを実行可能な関数のセットにフィルタリングします。関数が呼び出される引数と関数が受け取るパラメータの間に暗黙的な変換シーケンスが存在する実行可能な関数。

    void f(char);          // (1)
    void f(int ) = delete; // (2)
    void f();              // (3)
    void f(int& );         // (4)
    
    f(4); // 1,2 are viable (even though 2 is deleted!) 
          // 3 is not viable because the argument lists don't match
          // 4 is not viable because we cannot bind a temporary to 
          //     a non-const lvalue reference
    
  3. 最も有望な候補を選びなさい。生存関数F1別の実行可能な機能よりも優れ関数であるF2の各引数の暗黙的な変換シーケンス場合F1の対応する暗黙の変換シーケンスより悪くないF2 、そして...:

    3.1。いくつかの引数については、 F1その引数の暗黙の変換シーケンスは、 F2その引数の変換シーケンスよりも優れています。

    void f(int );  // (1)
    void f(char ); // (2)
    
    f(4);  // call (1), better conversion sequence
    

    3.2。ユーザー定義の変換では、 F1の戻り値から宛先タイプへの標準的な変換シーケンスは、戻りタイプのF2よりも優れた変換シーケンスです。

    struct A 
    {
        operator int();
        operator double();
    } a;
    
    int i = a; // a.operator int() is better than a.operator double() and a conversion
    float f = a; // ambiguous
    

    3.3。直接参照結合では、 F1F2と同じ種類の参照を持ちません。

    struct A 
    {
        operator X&();  // #1
        operator X&&(); // #2
    };
    A a;
    X& lx = a;  // calls #1
    X&& rx = a; // calls #2
    

    3.4。 F1は関数テンプレートの特殊化ではありませんが、 F2はまたは

    template <class T> void f(T ); // #1
    void f(int );                  // #2
    
    f(42); // calls #2, the non-template
    

    3.5。 F1F2はどちらも機能テンプレートの特殊化ですが、 F1F2よりも特殊化されています。

    template <class T> void f(T );  // #1
    template <class T> void f(T* ); // #2
    
    int* p;
    f(p); // calls #2, more specialized
    

ここでの順序は重要です。より良い変換シーケンスチェックは、テンプレートと非テンプレートチェックの間に行われます。これにより、転送参照のオーバーロードに関する一般的なエラーが発生します。

struct A {
    A(A const& ); // #1
    
    template <class T>
    A(T&& );      // #2, not constrained
};

A a;
A b(a); // calls #2!
        // #1 is not a template but #2 resolves to
        // A(A& ), which is a less cv-qualified reference than #1
        // which makes it a better implicit conversion sequence

最後に最善の実行可能候補が1つもない場合は、呼び出しが曖昧です。

void f(double ) { }
void f(float ) { }

f(42); // error: ambiguous

算数のプロモーションとコンバージョン

整数型を対応する昇格型に変換する方が、他の整数型に変換するよりも優れています。

void f(int x);
void f(short x);
signed char c = 42;
f(c); // calls f(int); promotion to int is better than conversion to short
short s = 42;
f(s); // calls f(short); exact match is better than promotion to int

floatdoubleすることを他の浮動小数点型に変換するよりも優れています。

void f(double x);
void f(long double x);
f(3.14f); // calls f(double); promotion to double is better than conversion to long double

プロモーション以外の算術コンバージョンはお互いに優れているわけでもなく悪いもありません。

void f(float x);
void f(long double x);
f(3.14); // ambiguous

void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous

したがって、標準型の整数引数または浮動小数点引数のいずれかを持つ関数fを呼び出す際にあいまいさがないようにするには、合計8つのオーバーロードが必要です。したがって、それぞれの可能な引数型に対して、オーバーロードの一致正確に指定するか、昇格された引数型のユニークなオーバーロードが選択されます。

void f(int x);
void f(unsigned int x);
void f(long x);
void f(unsigned long x);
void f(long long x);
void f(unsigned long long x);
void f(double x);
void f(long double x);

クラス階層内でのオーバーロード

次の例では、このクラス階層を使用します。

struct A { int m; };
struct B : A {};
struct C : B {};

派生クラス型から基本クラス型への変換は、ユーザー定義の変換よりも優先されます。これは、値渡しまたは参照渡しの場合、およびポインタの派生からポインタの派生への変換時に適用されます。

struct Unrelated {
    Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)

派生クラスから基本クラスへのポインタ変換も、 void*への変換よりも優れていvoid*

void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)

継承の同じチェーン内に複数のオーバーロードがある場合は、最も派生した基本クラスのオーバーロードが優先されます。これは、仮想ディスパッチと同様の原則に基づいています。「最も特化した」実装が選択されています。ただし、オーバーロードの解決は常にコンパイル時に発生し、暗黙的にダウンキャストされることはありません。

void f(const A& a);
void f(const B& b);
C c;
f(c); // calls f(const B&)
B b;
A& r = b;
f(r); // calls f(const A&); the f(const B&) overload is not viable

クラスに関して反則であるメンバへのポインタについては、反対の方向にも同様の規則が適用されます。最小派生派生クラスが優先されます。

void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)

安定性とボラティリティのオーバーロード

可能であれば、ポインタ引数をT*パラメータに渡すことは、 const T*パラメータに渡すよりも優れています。

struct Base {};
struct Derived : Base {};
void f(Base* pb);
void f(const Base* pb);
void f(const Derived* pd);
void f(bool b);

Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
       // constness is only a "tie-breaker" rule

同様に、 T&パラメータに引数を渡すことは、可能であれば、たとえ両方が完全一致のランクであっても、 const T&パラメータに渡すよりも優れています。

void f(int& r);
void f(const int& r);
int x;
f(x); // both overloads match exactly, but f(int&) is still better
const int y = 42;
f(y); // only f(const int&) is viable

この規則はconst修飾されたメンバ関数にも適用されます。非constオブジェクトへの変更可能なアクセスとconstオブジェクトへの不変なアクセスを許可することが重要です。

class IntVector {
  public:
    // ...
    int* data() { return m_data; }
    const int* data() const { return m_data; }
  private:
    // ...
    int* m_data;
};
IntVector v1;
int* data1 = v1.data();       // Vector::data() is better than Vector::data() const;
                              // data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
                              // data2 can't be used to modify the vector's data

同様に、揮発性過負荷は、不揮発性過負荷よりも好ましくない。

class AtomicInt {
  public:
    // ...
    int load();
    int load() volatile;
  private:
    // ...
};
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow