#include <string>
template <typename T>
class MyClass {
public:
MyClass() = default;
MyClass(T t) : value(t) {}
template <typename U>
MyClass(const MyClass<U>& other) : value(other.value) {}
T value;
};
template <typename T>
MyClass<T> create(T v) {
return MyClass<T>(v);
}
Также имеются две перегруженные функции:
void func(const MyClass<std::string> &) {}
void func(const MyClass<float> &) {}
Если попытаться скомпилировать такой код:
int main() {
func(create("Hello"));
return 0;
}
То получим "странное" сообщение об ошибке:
clang++ -Wall -Wextra -std=c++11 test.cpp -O3 -o test
test.cpp:28:5: error: call to 'func' is ambiguous
func(create("Hello"));
^~~~
test.cpp:16:6: note: candidate function
void func(const MyClass &) {
^
test.cpp:19:6: note: candidate function
void func(const MyClass &) {
^
1 error generated.
make: *** [all] Error 1
То есть компилятор считает обе функции подходящими вариантами и не знает какую выбрать. На самом деле понятно почему. У MyClass есть шаблонный конструктор копий, который может принимать любой тип. Найдя его при разрешении перегрузки функции func, компилятор даже не проверяет, можно ли реально вызвать этот конструктор, и не будет ли ошибок. А ошибки будут, т.к. const char* не приводится к float.Такая же проблема была с std::pair до принятие стандарта C++11. То есть такой код:
#include <utility>
void func(const pair<int, int>&) {}
void func(const pair<std::string, std::string>&) {}
int main(int argc, char* argv[]) {
func(std::make_pair("Hello", "world"));
return 0;
}
Приводил к такой же ошибке.Решить эту проблему можно с помощью SFINAE и enable_if:
template <typename T>
class MyClass {
public:
MyClass() = default;
MyClass(T t) : value(t) {}
template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
MyClass(const MyClass<U>& other) : value(other.value) {}
T value;
};
Теперь шаблонный конструктор копий участвует в перегрузке только в том случае, если тип T класса, который передаётся в конструктор конвертируется в тип T класса, который конструируется.
Если шаблонному классу std::enable_if в качестве первого параметра передали true, то внутри него определён typedef с типом второго парамтера. Если же первый параметр false, то typedef не определён.
Поле value в шаблонном классе std::is_convertible равно true или false в зависимости от того, конвертируется ли первый параметр шаблона во второй.
Итого, если в конструкции
std::enable_if<std::is_convertible<U, T>::value>::type>тип U конвертируется в тип T, то std::enable_if::type определён, и функция учавствует в разрешении перегрузки. В противном случае std::enable_if::type не определён, и вся конструкция является некорректной. Такая ситуация называется "substitution failure". По стандарту она не является ошибкой компиляции (is not an error), и компилятор просто выкидывает шаблонную функцию из рассмотрения. Данный приём называется SFINAE - substitution failure is not an error.
Комментариев нет:
Отправить комментарий