Ambiguous constructor error を解決するその他の方法
C++、C++17、language-lawyer における「Ambiguous constructor error in gcc but not in msvc」の解説
原因
このエラーは、コンパイラが複数のコンストラクタが呼び出し可能であると判断し、どのコンストラクタを選択するべきか判断できない場合に発生します。これは、以下のいずれかの理由によって起こります。
- 同じ引数を持つコンストラクタが複数存在する。
- 異なる引数を持つコンストラクタが存在し、コンパイラが暗黙的な型変換を行う必要がある。
C++17 での変更
C++17 では、language-lawyer
という属性が導入されました。この属性は、コンパイラに特定のコンストラクタを選択させるために使用できます。
解決方法
このエラーを解決するには、以下のいずれかの方法を使用できます。
- コンストラクタの引数を変更する。
language-lawyer
属性を使用して、コンパイラに特定のコンストラクタを選択させる。- コンパイラのオプションを変更して、コンストラクタの選択方法を指定する。
例
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
MyClass myClass(1.0); // エラー: Ambiguous constructor error
return 0;
}
このコードは、MyClass
クラスには int
型と double
型の引数を持つコンストラクタがそれぞれ存在するため、エラーが発生します。
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
MyClass(double x, bool b) {
std::cout << "MyClass(double x, bool b) called" << std::endl;
}
};
int main() {
MyClass myClass(1.0, true); // MyClass(double x, bool b) called
return 0;
}
#include <iostream>
class MyClass {
public:
[[language_lawyer]] MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
MyClass myClass(1.0); // MyClass(int x) called
return 0;
}
例 1: 同じ引数を持つコンストラクタ
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
MyClass myClass(1.0); // エラー: Ambiguous constructor error
return 0;
}
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
MyClass(const char* str) {
std::cout << "MyClass(const char* str) called" << std::endl;
}
};
int main() {
MyClass myClass("Hello"); // MyClass(const char* str) called
return 0;
}
このコードは、MyClass
クラスには int
型、double
型、const char*
型の引数を持つコンストラクタがそれぞれ存在します。main()
関数では、MyClass
クラスのコンストラクタに文字列リテラル "Hello" を渡しています。
コンパイラは、int
型、double
型、const char*
型のいずれのコンストラクタを選択するべきか判断できません。そのため、エラーが発生します。
例 3: language-lawyer 属性
#include <iostream>
class MyClass {
public:
[[language_lawyer]] MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
MyClass myClass(1.0); // MyClass(int x) called
return 0;
}
このコードは、MyClass
クラスの int
型の引数を持つコンストラクタに language_lawyer
属性を付与しています。
この属性を付与することで、コンパイラにこのコンストラクタを選択させることができます。そのため、エラーが発生せずにコンパイルされます。
例 4: コンパイラのオプション
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
MyClass myClass(static_cast<int>(1.0)); // MyClass(int x) called
return 0;
}
このコードでは、double
型の値 1.0 を static_cast
を使用して int
型に変換しています。
using ディレクティブを使用する
using ディレクティブを使用して、特定のコンストラクタを名前空間の先頭に配置することができます。
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
using namespace MyClass;
int main() {
MyClass myClass(1.0); // MyClass(int x) called
return 0;
}
このコードでは、MyClass
クラスの using ディレクティブを使用しています。
これにより、MyClass
クラスの名前空間の先頭に MyClass(int x)
コンストラクタが配置されます。
デフォルトコンストラクタを削除する
#include <iostream>
class MyClass {
public:
MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
MyClass() = delete; // デフォルトコンストラクタを削除
};
int main() {
// MyClass myClass; // エラー: デフォルトコンストラクタが削除されています
MyClass myClass(1.0); // MyClass(int x) called
return 0;
}
このコードでは、MyClass
クラスのデフォルトコンストラクタを delete
キーワードを使用して削除しています。
コンストラクタをexplicitにする
コンストラクタを explicit
キーワードを使用して明示的に指定することで、コンパイラが暗黙的な型変換によるコンストラクタ呼び出しを抑制することができます。
#include <iostream>
class MyClass {
public:
explicit MyClass(int x) {
std::cout << "MyClass(int x) called" << std::endl;
}
MyClass(double x) {
std::cout << "MyClass(double x) called" << std::endl;
}
};
int main() {
// MyClass myClass(1.0); // エラー: コンストラクタがexplicitです
MyClass myClass(static_cast<int>(1.0)); // MyClass(int x) called
return 0;
}
このコードでは、MyClass
クラスの int
型の引数を持つコンストラクタを explicit
キーワードを使用して明示的に指定しています。
Ambiguous constructor error は、コンパイラによって異なるコンストラクタが選択されるために発生するエラーです。
このエラーを解決するには、いくつかの方法があります。
- language-lawyer 属性を使用する
- コンパイラのオプションを変更する
c++ c++17 language-lawyer