Почему SFINAE не выбирает перегрузку при получении константных ссылок?
В этом коде
struct A {int commmon; int rare;};
struct B {int commmon;};
struct L {
template<class T>
int f(const T& t) {return t.commmon;}
template<class T>
int f(T& t) {return t.rare;}
};
void func() {
A a; B b; L l;
l.f(a);
l.f(B{});
l.f(b);
}
последние строки дают мне ошибку
In instantiation of ‘int L::f(T&) [with T = B]’:
error: ‘struct B’ has no member named ‘rare’
Но согласно моему пониманию SFINAE, вторая перегрузка должна игнорироваться из-за сбоя замещения в организме. Почему этого не происходит?
РЕДАКТИРОВАТЬ: если я изменяю тип возврата второй перегрузки на decltype(T::rare)
, он делает то, что я хочу. Так, где мой SF должен быть NAE?
3 ответа
Наименее подробный способ исправить это использовать auto
тип возврата с завершающим типом возврата при перегрузке с большим ограничением:
struct L {
template <class T>
auto f(const T& t) {return t.commmon;}
template <class T>
auto f(T& t) -> decltype(t.rare) {return t.rare;}
};
Преимущество этого подхода заключается в том, что ограничение указывается в точке, где компилятор уже видел аргумент функции, что позволяет использовать более короткую запись, чем std::enable_if
пункты в объявлении параметров шаблона:
#include <type_traits>
struct L {
template <class T>
int f(const T& t) {return t.commmon;}
template <class T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, int> = 0>
int f(T& t) { return t.rare;}
};
Обратите внимание, что более ограниченная функция не будет вызываться при передаче аргумента rvalue. Возможно, вы захотите исправить это, изменив сигнатуры функций на
template<class T /*, ... */>
int f(T&& t) { /* ... */ }
SFINAE не распространяется на функциональные тела [temp.deduct / 8]:
Только недопустимые типы и выражения в непосредственном контексте типа функции, ее типов параметров шаблона и его явного спецификатора могут привести к ошибке вывода.
Аргументы шаблона выводятся до того, как будет создана функция. На момент оценки реализации вычет уже произошел. Таким образом, все, что имеет отношение к дедукции, этосигнатура функции, и из существующих, неконстантный вариант подходит для выбора.
Если вы хотите применить, получите SFINAE в зависимости от существующих членов, вы должны сделать это уже в сигнатуре функции. lubgr хорошо отражает это: тип возвращаемого значения decltype(t.rare)
; если Т не обеспечивает rare
член, тип возврата не может быть выведен и, следовательно, перегрузка не учитывается при разрешении.
Нашли два других ответа на интересующий вас вопрос: C++ 11, совместимый с pre-C++11