Почему Разрешение перегрузки предпочитает неограниченную функцию шаблона по сравнению с более конкретной? - PullRequest
14 голосов
/ 29 мая 2019

У меня есть эта минимальная библиотека шаблонов выражений с умножением, т. Е.

template <typename T, typename U>
struct mul {
    const T &v1;
    const U &v2;
};

template <typename T, typename U>
mul<T, U> operator*(const T &one, const U &two) {
    std::cout << " called: mul<T, U> operator*(const T &one, const T &two)\n";
    return mul<T, U>{one, two};
}

и транспонированием, т. Е.

template <typename T>
struct transpose {
    const T &t;
};

template <typename T>
transpose<T> tran(const T &one) {
    return transpose<T>{one};
}

Я представлю некоторые типы A и B,где последний является подклассом первого:

template <typename T>
struct A {
    T elem;
};

template <typename T>
struct B : A<T> {
    B(T val) : A<T>{val} {}
};

Затем я могу вызвать мою библиотеку шаблонов выражений следующим образом (с перегрузкой для печати на std::cout):

template <typename T, typename U>
std::ostream &operator<<(std::ostream &os, const mul<T, U> &m) {
    os << " unconstrained template \n";
}

int main(int argc, char const *argv[]) {
    B<double> a{2};
    B<double> b{3};
    std::cout << tran(a) * b << "\n";
    return 0;
}

Это дает мне вывод:

called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template 

Пока все хорошо.Предположим теперь, что я хочу специализированный режим для «транспонирования переменной типа A<T>, умноженной на переменную типа A<T> для некоторого типа T», как это было в моем main.Для этого я введу

template <typename T>
T operator*(const transpose<A<T>> &one, const A<T> &two) {
    std::cout << " called: T operator*(const A<T> &one, const A<T> &two)\n";
    return one.t.elem * two.elem;
}

Я запускаю ту же функцию main, что и выше, и все равно получаю тот же вывод, что и выше (unconstrained template).Этого следует ожидать, поскольку transpose<B<double>> - это совершенно другой тип по сравнению с transpose<A<double>>, поэтому при разрешении перегрузки выбирается неограниченная версия шаблона operator*.

(Конечно, если я изменю свои определения переменных в main на A вместо B, ADL вызовет специализированную функцию и выдает called: T operator*(const A<T> &one, const A<T> &two) и 6).

Я недавно узнал о SFINAE, поэтому я ожидал, что следующее изменение в более конкретном операторе умножения вызовет перегрузку при перегрузке для выбора специализированной функции:

template <typename T, typename V>
std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const transpose<V> &one,
                                                               const V &two) {
    std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
                 "transpose<V> &one, const V &two)\n";
    return one.t.elem * two.elem;
}

Даже при использовании SFINAE'd operator* Я все еще получаю unconstrained template версию.Как так?Какие изменения я должен сделать, чтобы вызвать более специализированную функцию шаблона?

1 Ответ

12 голосов
/ 29 мая 2019

Проблема заключается в том, что при перегрузке SFINAE T используется в не выводимом контексте. Вы фактически спрашиваете компилятор: «Включите это, если существует T такой, что A<T> является базовым классом V». Экзистенциальная количественная оценка является хорошим индикатором того, что то, что вы запрашиваете, не может быть SFINAEd.

Вы можете увидеть это сами, если отключите шаблон без ограничений, как я сделал здесь . Это заставляет компилятор объяснить, почему другая функция недопустима.

Вы можете решить эту проблему, сделав T доступным через классы A (и, следовательно, B), например:

template <typename T>
struct A {
    using Type = T;
    T elem;
};


template <typename V>
std::enable_if_t<std::is_base_of<A<typename V::Type>, V>::value, typename V::Type> operator*(const transpose<V> &one,
                                                               const V &two) {
    std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
                 "transpose<V> &one, const V &two)\n";
    return one.t.elem * two.elem;
}

[Живой пример]

...