C++の関数オーバーロードにおける初期化子の優先順位

問題提起

以下のコードを見て、どの関数が呼ばれるか考えてみましょう。

#include <iostream>
#include <vector>

enum Color { Red = 1 };

void display(std::vector<int> vec) {
    std::cout << "Vector function called" << std::endl;
}

void display(Color c) {
    std::cout << "Enum function called: " << c << std::endl;
}

int main() {
    display({});
    return 0;
}

多くの人は、{} は空のベクターを表すので、display(std::vector<int>) が呼ばれると考えるでしょう。

しかし、実際に実行すると、出力は「Enum function called: 0」となります。

分析

この挙動を理解するために、まず列挙型の初期化を確認しましょう:

Color c = {};
std::cout << "Color value: " << c << std::endl;

このコードはコンパイル可能で、「Color value: 0」と出力します。これは、列挙型が {} で初期化できることを示しています。

実は、C++ではほとんどの基本型が {} で初期化可能です。コンパイラは、std::vector<int> を初期化するより Color を初期化する方が「より直接的な一致」だと判断し、display(Color) を選択します。

もし display(Color) をコメントアウトし、代わりに display(int) を追加すると:

void display(int num) {
    std::cout << "Integer function called: " << num << std::endl;
}

今度は display({}) で「Integer function called: 0」と出力されます。

初期化方法の比較

この問題は、C++の異なる初期化方法の挙動に関連しています:

double value1 = 3.14;
int num1 = value1;       // OK: 暗黙の変換
int num2 = {value1};     // エラー: 縮小変換は禁止
int num3(value1);        // OK: 暗黙の変換

リスト初期化({}を使用)は縮小変換を禁止するため、より安全です。しかし、C言語では挙動が異なります:

double value = 3.14;
int num1 = value;        // OK
int num2 = {value};      // OK
int num3(value);         // エラー

C言語ではリスト初期化がサポートされ、複合リテラルとしても使用できます。この違いは、C++がCから継承した特性と、C++が追加した安全性のバランスを反映しています。

実践的な例

この挙動は、より複雑なコードで予期せぬバグを引き起こす可能性があります:

#include <iostream>
#include <string>
#include <vector>

enum class Status { Success = 0, Error = 1 };

void process(std::vector<std::string> data) {
    std::cout << "Processing vector with " << data.size() << " elements" << std::endl;
}

void process(Status s) {
    std::cout << "Processing status: " << static_cast<int>(s) << std::endl;
}

int main() {
    // 意図は空のベクターを渡すこと
    process({});
    
    // 明示的にベクターを渡す場合
    process(std::vector<std::string>{});
    
    // 明示的にステータスを渡す場合
    process(Status{});
    
    return 0;
}

このコードでは、process({})process(Status) を呼び出しますが、process(std::vector<std::string>{}) は明示的にベクターバージョンを呼び出します。

タグ: C++ 関数オーバーロード 初期化 列挙型 リスト初期化

6月11日 23:47 投稿