関数のオーバーロード
もし、異なる型に対応する関数が必要になった場合はどうすればよいでしょうか?
addInt、addDouble、addFloatと記述することもできますが、それは面倒です。関数のオーバーロードを使えば、複数の関数に同じ名前を付けることができ、コンパイラが引数に基づいて適切な関数を選択してくれます。
1. 関数のオーバーロードとは何か?
関数のオーバーロードとは、同じ名前でありながら、異なる引数リスト(引数の数や型が異なる)を持つ複数の関数を定義することを指します。
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int main() {
std::cout << add(3, 5) << std::endl; // Calls int version
std::cout << add(3.14, 2.86) << std::endl; // Calls double version
return 0;
}
コンパイラは、渡された引数に基づいて適切な関数を選択します。
2. オーバーロードのルール
(1) 2.1 パラメータ数の違い
void print(int a) { ... }
void print(int a, int b) { ... }
void print(int a, int b, int c) { ... }
(2) 2.2 パラメータの種類
void print(int a) { ... }
void print(double a) { ... }
void print(std::string a) { ... }
(3) 2.3 対象外となるもの
以下の方法ではオーバーロードすることはできません:
- 戻り値の型のみ:
int foo()とdouble foo()— コンパイラはどちらを指しているのか判別できません - パラメータ名:
int foo(int x)およびint foo(int y)— 名前は任意です
// ❌ These are NOT overloads — same signature
int foo(int a);
double foo(int a); // Error: already defined
3. オーバーロードの解決
オーバーロードされた関数を呼び出すと、コンパイラは以下の手順を実行します。
- 完全一致 — 引数の型と完全に一致する関数を検索します
- プロモーション — 数字によるプロモーションを試みます(例:
int→double) - 変換 — 標準的な変換を試みます(例:
int→long) - ユーザー定義の変換 — コンストラクタまたは変換演算子を試す
(1) ▶ サンプル:コンパイラがどのように判断するか
#include iostream
void print(int x) {
std::cout << "Integer: " << x << std::endl;
}
void print(double x) {
std::cout << "Double: " << x << std::endl;
}
int main() {
print(42); // Exact match → int version
print(3.14); // Exact match → double version
print('A'); // char can promote to int → int version
return 0;
}
4. デフォルト引数を用いたオーバーロード
注意してください。オーバーロードとデフォルト引数を組み合わせると、曖昧さが生じる可能性があります。
// ❌ Ambiguous call
void func(int a) { ... }
void func(int a, int b = 10) { ... }
int main() {
func(5); // ❌ Both functions match! Compiler error
return 0;
}
5. 実践例
▶ サンプル 1: オーバーロードされた Max 関数 (難易度 ⭐⭐)
#include iostream
int max(int a, int b) {
return (a > b) ? a : b;
}
double max(double a, double b) {
return (a > b) ? a : b;
}
int max(int a, int b, int c) {
return max(max(a, b), c);
}
int main() {
std::cout << "max(3, 7): " << max(3, 7) << std::endl;
std::cout << "max(3.14, 2.86): " << max(3.14, 2.86) << std::endl;
std::cout << "max(3, 7, 5): " << max(3, 7, 5) << std::endl;
return 0;
}
▶ サンプル 2: オーバーロードされた表示関数 (難易度 ⭐⭐)
#include iostream
#include string
#include vector
void display(int value) {
std::cout << "Integer: " << value << std::endl;
}
void display(double value) {
std::cout << "Double: " << value << std::endl;
}
void display(const std::string& value) {
std::cout << "String: " << value << std::endl;
}
void display(const std::vectorint& values) {
std::cout << "Vector: [";
for (size_t i = 0; i < values.size(); i++) {
std::cout << values[i];
if (i < values.size() - 1) std::cout << ", ";
}
std::cout << "]" << std::endl;
}
int main() {
display(42);
display(3.14);
display("Hello C++");
display({1, 2, 3, 4, 5});
return 0;
}
6. 演算子のオーバーロード(プレビュー)
C++ では、独自の型に対して +、-、<< などの演算子をオーバーロードすることもできます。これは関数のオーバーロードと密接に関連しています。
// Preview: overloading the << operator for a custom struct
struct Point {
int x, y;
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}
演算子のオーバーロードについては、後で詳しく説明します。とりあえず、ここでも同じオーバーロードの仕組みが適用されることを覚えておいてください。
❓ よくある質問
A::foo() と B::foo() を定義することができます。これらは異なる関数であり、同じ関数のオーバーロードではありません。📖 まとめ
- 関数のオーバーロード により、パラメータが異なる複数の関数に同じ名前を付けることができます
- コンパイラは、コンパイル時に引数の型に基づいて適切なオーバーロードを選択します(静的ポリモーフィズム)。
- オーバーロードは、パラメータの数が異なる場合、またはパラメータの型が異なる場合に行うことができます。
- 戻り値の型のみ、あるいは引数の名前のみでは、オーバーロードすることはできません。
- オーバーロードとデフォルトパラメータを混同しないように注意してください。曖昧さが生じる可能性があります。
- 演算子のオーバーロードも同様の仕組みを利用しています
📝 練習問題
-
初心者(難易度 ⭐):
printのオーバーロード関数を 3 つ作成してください。1 つはint用、1 つはdouble用、もう 1 つはstd::string用です。それぞれ、型と値を表示するようにしてください。 -
中級(難易度 ⭐⭐): 2つのオーバーロードされた
average関数を記述してください。1つは2つのdoubleパラメータを取り、もう1つは3つのパラメータを取ります。これらをテストしてください。 -
上級(難易度 ⭐⭐⭐): オーバーロードされた
convert関数をいくつか作成してください。1つは摂氏を華氏に変換し、もう1つは華氏を摂氏に変換するものです。両方の変換を実演してください。
7. 🚀 次は
次に、スコープと有効期間(第14課)について学びます。変数がどこに存在し、どのくらいの期間存続するのかを理解します。



