2015-12-22 11 views
14

Chcę napisać funkcję, która przeprowadzić podział pomiędzy dwoma argumentami a i b różnego typu, używając wyrażenia a/b jeżeli operator podział jest określony, lub spaść z powrotem w a * (1/b) jeśli istnieje nie jest takim operatorem.SFINAE awaryjna jeśli operator podział nie jest realizowany

To co myślałem, ale nie wiem jak wyłączyć drugą definicję (lub priorytet pierwszy), gdy oba * i / operatorzy są zdefiniowane:

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); 
} 
+0

Dobrze, co próbowaliście? – edmz

Odpowiedz

17

Najprostszą sztuczką jest poleganie na rozdzielczości przeciążenia, która już określa jej zasady pierwszeństwa. W poniższym rozwiązaniu, z dodatkowym argumentem, jest on lepszy niż 0 -> char, a zatem ten pierwszy będzie preferowany, jeśli nie zostanie wykluczony przez wyrażenie SFINAE, a drugi będzie nadal zdolny do wywołania zastępczego.

#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

Jeśli miał więcej przeciążeń, można zamiast wprowadzać typ pomocnika priorytet każdego z nich: znowu

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

Tutaj rank<2> -> rank<2> jest lepsza niż rank<2> -> rank<1> który z kolei jest preferowany do rank<2> -> rank<0>

+0

Myślę, że powinieneś dodać trochę wyjaśnienia, ponieważ OP powiedział _ "ale nie wiem, jak wyłączyć drugą definicję (lub pierwszeństwo)" _ – edmz

+0

Czuję, że zamrugałem i świat metaprogramowania przesunął się z wysyłki do końcowe błędy SFINAE + preferowane niejawne konwersje, kiedy to się stało? –

+0

jeśli zdefiniowano 'operator/(T, U), pierwszy zostanie wywołany, ponieważ nie ma konwersji danych dla' 0' – Danh

2

Nieco brzydki, ale działa dla mnie pod 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); 
} 

Chodzi o to, aby stworzyć jeden bardziej wyspecjalizowany niż drugi. Potrzebujemy częściowej specjalizacji, a tym samym klasy pomocniczej (funktora). Następnie mamy ogólną klasę, która używa mnożenia i wyspecjalizowaną klasę, która używa podziału, ale tylko jeśli jest to dozwolone.

ocenia się na int, ale tylko jeśli something jest poprawna.

Jestem pewien, że można to zrobić łatwiej.

5

Powinieneś wykonać jedną z opcji lepiej, jeśli obie mogą się kompilować. Na przykład:

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

Tutaj, jeśli nie ma podziału, druga funkcja jest jedyną dostępną funkcją. Jeżeli podział jest dostępna, to nie są 2 funkcje:

helper(T, U, int) i helper(T, U, ...) a pierwszym z nich jest lepszy mecz dla wywołania helper(t, u, 1)

DEMO

Zauważ, że możesz użyć doskonałe przekazywanie w smart_division, Pominąłem to dla jasności

+0

Z tego samego powodu pominąłem również idealne przekazywanie w pytaniu. – pqnet