2015-12-22 13 views
14

voglio scrivere una funzione che esegue una divisione tra due argomenti a e b di tipo diverso, utilizzando l'espressione a/b se l'operatore di divisione è definito, o ricadere nel a * (1/b) caso non è un operatore del genere.SFINAE fallback se operatore di divisione non è implementata

Questo è quello che ho pensato, ma non so come disattivare la seconda definizione (o dare la priorità alla prima) quando entrambi gli operatori * e / sono definiti:

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

Ok, che hai provato? – edmz

risposta

17

Il trucco più semplice è affidarsi alla risoluzione di sovraccarico che già definisce le sue regole di precedenza. Nella soluzione seguente, con un argomento aggiuntivo 0, 0 -> int è migliore di 0 -> char, quindi, il primo sarà il preferito se non escluso da un'espressione SFINAE e quest'ultimo è ancora utilizzabile per una chiamata di fallback.

#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

Se tu avessi più sovraccarichi, si potrebbe invece introdurre un tipo di supporto per dare la priorità ciascuno:

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

Anche in questo caso, rank<2> -> rank<2> è meglio di rank<2> -> rank<1> che a sua volta è preferito a rank<2> -> rank<0>

+0

Penso che dovresti aggiungere qualche spiegazione, dal momento che l'OP diceva "ma non so come disabilitare la seconda definizione (o dare la priorità al primo)" _ – edmz

+0

Mi sento come se avessi battuto le palpebre e il mondo della metaprogrammazione si spostasse dalla spedizione dei tag a fallimenti di SFINAE con ritorno finale + conversioni implicite preferite, quando è successo? –

+0

se 'operator/(T, U)' è definito, il primo sarà chiamato perché nessuna conversione di dati per '0' – Danh

2

Un po 'brutto, ma le opere per me sotto 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); 
} 

Il punto è rendere uno più specializzato dell'altro. Quindi abbiamo bisogno di una specializzazione parziale e quindi di una classe helper (un funtore). Dopo questo, abbiamo una classe generale che usa la moltiplicazione e una classe specializzata che usa la divisione, ma solo se è consentita.

decltype(something, 1) valutazioni a int, ma solo se something è corretto.

Sono sicuro che questo può essere fatto più facile.

5

Si dovrebbe fare una delle opzioni preferibili se entrambi possono essere compilati. Ad esempio:

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

Qui se non è disponibile alcuna divisione, la seconda funzione è l'unica funzione disponibile. Se la divisione è disponibile, poi ci sono 2 funzioni:

helper(T, U, int) e helper(T, U, ...) e il primo è migliore corrispondenza per la chiamata helper(t, u, 1)

DEMO

nota, che si consiglia di utilizzare l'inoltro perfetto in smart_division, L'ho saltato per chiarezza

+0

Ho saltato l'inoltro perfetto anche nella domanda, per lo stesso motivo. – pqnet

Problemi correlati