Я пытался удалить некоторые неиспользуемые перегрузки и вызвал ошибку компиляции, которая, как сказал компилятор, была ошибкой замены шаблона. Но я подумал: «Сбой замещения — это не ошибка», и вообще, зачем устранять перегрузку?
Простые начинания:
#include <string>
int ParseInt(const char *);
int ParseInt(std::string);
bool F(int(*)(const char *));
bool User() {
return F(ParseInt);
}
Здесь User () вызывает F с адресом процедуры синтаксического анализа. Все хорошо. ParseInt перегружен, но только одна перегрузка соответствует подписи F.
Введите шаблонную перегрузку F:
bool F(int(*)(const char *));
template <typename T>
struct MetaDeduce {
typedef typename T::X type;
};
template <typename T>
typename MetaDeduce<T>::type F(const T&);
Теперь есть странная перегрузка шаблона F, но это нормально, потому что указатели на функции в любом случае не имеют членов с именем X. Все компилируется, все хорошо.
ДО ТЕХ ПОР….
#include <string>
int ParseInt(const char *);
// int ParseInt(std::string); // commenting this out caused a compiler error!
bool F(int(*)(const char *));
template <typename T>
struct MetaDeduce {
typedef typename T::X type;
};
template <typename T>
typename MetaDeduce<T>::type F(const T&);
bool User() {
return F(ParseInt);
}
Как видно на Годболт ( http://goo.gl/2Yd04p ), это приводит к странной ошибке компиляции:
10 : error: type 'int (const char *)' cannot be used prior to '::'
because it has no members
typedef typename T::X type;
^
14 : note: in instantiation of template class 'MetaDeduce<int (const char *)>'
requested here
typename MetaDeduce<T>::type F(const T&);
^
WTF ??? Похоже, что компилятор жалуется на ошибку замещения, но почему раньше это не было проблемой? И вообще, я думал, что ошибка замены не была ошибкой! В чем дело?
Это вызвано тонким взаимодействием между двумя языковыми свойствами
SFINAE применяется только в непосредственный контекст. Ваш MetaDeduce<T>
не определяется в таком непосредственном контексте, и это делает typename MetaDeduce<T>::type
серьезная ошибка. Но SFINAE относится к typename T::X
и использование этого скомпилирует ваш код даже с ParseInt(std::string)
перегрузка закомментирована.
template <typename T>
typename T::X F(const T&);
Живой пример (обратите внимание, что вы получаете ошибки компоновщика, потому что вы не определили свои функции)
Так что если typename MetaDeduct<T>::type
виновник, почему он работал с двумя перегрузками ParseInt
? Хорошо, рассмотрим, что произойдет, если у вас будет только шаблон F
и оба ParseInt
s. И для удобства, дайте этот шаблон F
тип возврата bool для игнорирования на данный момент ошибки замещения.
int ParseInt(const char *);
int ParseInt(std::string);
template <typename T>
bool F(T const&);
bool User() { return F(ParseInt); } // error, cannot deduce template argument
С двумя перегрузками ParseInt
и никакой дополнительной информации в звонке F(ParseInt)
, компилятор не может определить, какая версия ParseInt
должен соответствовать параметру шаблона T
, В контексте этого фрагмента это приведет к серьезной ошибке (потому что у вас будет пустой набор перегрузки), но с дополнительной перегрузкой без шаблона F(int(*)(const char*))
это не будет (потому что ParseInt(const char*)
будет соответствовать этому). Обратите внимание, что, поскольку вывод аргументов не удается здесь, подстановка аргумента даже не будет иметь место в случае, если тип возвращаемого значения был typename MetaDeduce<T>::Type
,
По сути, две перегрузки ParseInt
защитил вас от неудачи замещения в не непосредственном контексте. Как только вы убираете один из них, вывод аргумента завершается успешно, и не немедленная замена подстановки приводит к серьезной ошибке.
Это немного похоже на пересечение улицы с красным светом и спасение, потому что два встречных грузовика сталкиваются друг с другом, прежде чем они могут ударить вас. Только с одним грузовиком тебя бьют.
Других решений пока нет …