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
верен.
Я уверен, что это можно сделать проще.