Почему 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

Другие вопросы по тегам