SFINAE, если оператор разделения не реализован

Я хочу написать функцию, которая выполняет разделение между двумя аргументами a и b другого типа, используя выражение a/b, если оператор деления определен или возвращается в a * (1/b), если там не является таким оператором.

Это то, о чем я думал, но я не знаю, как отключить второе определение (или приоритет первого), когда определены операторы * и /:

template<typename T, typename U>
auto smart_division(T a, U b) -> decltype (a/b) {
    return a/b;
}
template<typename T, typename U>
auto smart_division(T a, U b) -> decltype (a * (U(1)/b)) {
    return a * (U(1)/b);
}

Ответы

Ответ 1

Самый простой трюк заключается в том, чтобы полагаться на разрешение перегрузки, которое уже определяет его правила приоритета. В приведенном ниже решении, с дополнительным аргументом 0, 0 -> int лучше, чем 0 -> char, следовательно, первый будет предпочтительным, если не исключить выражение SFINAE, а последнее по-прежнему жизнеспособно для обратного вызова.

#include <utility>

template <typename T, typename U>
auto smart_division_impl(T a, U b, int) -> decltype(a/b)
{
    return a/b;
}

template <typename T, typename U>
auto smart_division_impl(T a, U b, char) -> decltype(a * (U(1)/b))
{
    return a * (U(1)/b);
}

template <typename T, typename U>
auto smart_division(T&& a, U&& b) -> decltype(smart_division_impl(std::forward<T>(a), std::forward<U>(b), 0))
{
    return smart_division_impl(std::forward<T>(a), std::forward<U>(b), 0);
}

DEMO

Если у вас было больше перегрузок, вместо этого вы могли бы ввести вспомогательный тип для определения приоритетов каждого из них:

template <int I> struct rank : rank<I-1> {};
template <> struct rank<0> {};

template <typename T, typename U>
auto smart_division_impl(T a, U b, rank<2>) -> decltype(a/b) 
//                                 ~~~~~~^ highest priority
{
    return a/b;
}

template <typename T, typename U>
auto smart_division_impl(T a, U b, rank<1>) -> decltype(a * (U(1)/b))
//                                 ~~~~~~^ mid priority
{
    return a * (U(1)/b);
}

template <typename T, typename U>
int smart_division_impl(T a, U b, rank<0>)
//                                ~~~~~~^ lowest priority
{
    return 0;
}

template <typename T, typename U>
auto smart_division(T&& a, U&& b) -> decltype(smart_division_impl(std::forward<T>(a), std::forward<U>(b), rank<2>{}))
{
    return smart_division_impl(std::forward<T>(a), std::forward<U>(b), rank<2>{});
}

DEMO 2

Здесь снова rank<2> -> rank<2> лучше, чем rank<2> -> rank<1>, что в свою очередь предпочтительнее rank<2> -> rank<0>

Ответ 2

Вы должны сделать один из вариантов предпочтительным, если оба могут скомпилировать. Например:

#include <iostream>

template<typename T, typename U>
auto helper(T a, U b, int) -> decltype (a/b) {
    std::cout << "first";
    return a/b;
}

template<typename T, typename U>
auto helper(T a, U b, ...) -> decltype (a * (U(1)/b)) {
    std::cout << "second";
    return a * (U(1)/b);
}

template<typename T, typename U>
auto smart_division(T a, U b) -> decltype (helper(a, b)) {
    return helper(a, b, 0);
}


struct Test {
    explicit Test(int) {}
};
int operator / (Test a, Test b) {
return 1;
}

int main() {
    std::cout << smart_division(1.0, 2.0);
    Test t{5};
    std::cout << smart_division(1, t);
    return 0;
}

Здесь, если нет деления, вторая функция является единственным доступным функционалом. Если деление доступно, то есть 2 функции:

helper(T, U, int) и helper(T, U, ...), а первая лучше подходит для вызова helper(t, u, 1)

DEMO

Обратите внимание, что вы можете использовать идеальную пересылку в smart_division, я пропустил ее для ясности

Ответ 3

Несколько уродливый, но работает для меня под gcc 5.2.0 С++ 14:

template<typename T, typename U, class R = int>
struct smart_division_helper {
    auto operator() (T a, U b) -> decltype (a * (U(1)/b))  {
        return a*(U(1)/b);
    }
};

template<typename T, typename U>
struct smart_division_helper<T, U, decltype(declval<T>()/declval<U>(), 1)> {
    auto operator() (T a, U b) -> decltype (a/b) {
        return a/b;
    }
};

template<class T, class U>
auto smart_division(T a, U b) -> decltype (smart_division_helper<T,U,void>()(a,b)) {
    return smart_division_helper<T,U,int>()(a,b);
}

Дело в том, чтобы сделать еще одну специализацию, чем другую. Поэтому нам нужна частичная специализация и, следовательно, вспомогательный класс (функтор). После этого у нас есть общий класс, который использует умножение и специализированный класс, который использует деление, но только если он разрешен.

decltype(something, 1) оценивается как int, но только если something верен.

Я уверен, что это можно сделать проще.