17

I want to write a function which perform a division between two arguments a and b of different type, using the expression a/b if the division operator is defined, or fall back in the a * (1/b) if there is no such operator.

This is what i thought of, but i don't know how to disable the second definition (or prioritize the first) when both * and / operators are defined:

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);
}
Petr
  • 9,812
  • 1
  • 28
  • 52
pqnet
  • 6,070
  • 1
  • 30
  • 51

3 Answers3

22

The simplest trick is to rely on overload resolution which already defines its rules of precedence. In the below solution, with an additional argument 0, 0 -> int is better than 0 -> char, hence, the former will be the preferred one if not excluded by an expression SFINAE, and the latter still viable for a fallback call.

#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

If you had more overloads, you could instead introduce a helper type to prioritize each one:

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

Here again, rank<2> -> rank<2> is better than rank<2> -> rank<1> which in turn is preferred to rank<2> -> rank<0>

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • I think you should add some explanation, since the OP said _"but i don't know how to disable the second definition (or prioritize the first)"_ – edmz Dec 22 '15 at 15:59
  • I feel like I blinked and the metaprogramming world shifted from tag dispatch to trailing return SFINAE failures + preferred implicit conversions, when did this happen? – Nir Friedman Dec 22 '15 at 16:05
  • if `operator/(T,U)` defined, the first one will be call because no data conversion for `0` – Danh Dec 22 '15 at 16:08
  • Now that I read the solution it comes to me that I should have known that because I read something similar somewhere in the past, and I feel silly. I select this answer because it explain also the generic way to extend the approach to multiple priorities. On a side note, wouldn't it be useful to have something similar to the `rank` template in the standard library, so that there would be a standard solution to such problems? – pqnet Dec 22 '15 at 18:02
  • 1
    wouldn't be perfect forwarding necessary also in the `smart_division_impl` functions? – pqnet Dec 22 '15 at 18:03
5

You should make one of options preferable if both can compile. For example:

#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;
}

Here if there is no division available, the second function is the only available funciton. If division is available, then there are 2 functions:

helper(T, U, int) and helper(T, U, ...) and the first one is better match for call helper(t, u, 1)

DEMO

Note, that you may want to use perfect forwarding in smart_division, I skipped it for clarity

RiaD
  • 46,822
  • 11
  • 79
  • 123
2

Somewhat ugly, but works for me under gcc 5.2.0 c++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);
}

The point is to make one more specialized than the other. So we need partial specialization, and thus helper class (a functor). After this, we have a general class that uses multiplication, and a specialized class that uses division, but only if it is allowed.

decltype(something, 1) evaluates to int, but only if something is correct.

I am sure this can be done easier.

Petr
  • 9,812
  • 1
  • 28
  • 52