2013-04-09 12 views
26

Więc chcę pisać automatycznego !=:Jak uniknąć tego zdania w szablonie SFINAE?

template<typename U, typename T> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

ale to niegrzeczne . Więc piszę

// T() == U() is valid? 
template<typename T, typename U, typename=void> 
struct can_equal:std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

który jest klasa cechy typ, który mówi „to t == u ważny kod, który zwraca typu cabrio do bool”.

więc poprawić swój !=:

template<typename U, typename T, 
    typename=typename std::enable_if<can_equal<T,U>::value>::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

a teraz to tylko poprawne przesłanianie jeśli == istnieje. Niestety, jest to nieco chciwy:

struct test { 
}; 
bool operator==(const test&, const test&); 
bool operator!=(const test&, const test&); 

jak będzie snarf się prawie każdy test() != test() zamiast powyższego != miano. Myślę, że nie jest to pożądane - wolę nazwać jednoznacznie != niż automatyczne przekazywanie do == i negować.

Tak, piszę tę klasę cech:

template<typename T, typename U,typename=void> 
struct can_not_equal // ... basically the same as can_equal, omitted 

który sprawdza czy T != U jest prawidłowy.

Następnie rozszerz != następująco:

template<typename U, typename T, 
    typename=typename std::enable_if< 
    can_equal<T,U>::value 
    && !can_not_equal<T,U>::value 
    >::type 
> 
bool operator!=(U&& u, T&& t) { 
    return !(std::forward<U>(u) == std::forward<T>(t)); 
} 

które jeśli analizować je, mówi: „To zdanie jest fałszywe” - operator!= istnieje między T i U wtw operator!= nie istnieje między T i U .

Nie jest niespodzianką, że każdy kompilator przetestowałem segfaults, gdy karmione to. (clang 3.2, gcc 4.8 4.7.2 intel 13.0.1). Podejrzewam, że to, co robię jest nielegalne, ale chciałbym zobaczyć standardowe odniesienie. (edytuj: To co robię jest nielegalne, ponieważ indukuje nieograniczoną ekspansję szablonów rekursywnych, ponieważ ustalenie, czy moja aplikacja ma zastosowanie !=, wymaga sprawdzenia, czy mój != ma zastosowanie. Wersja powiązana w komentarzach z #if 1 daje sensowny błąd).

Ale moje pytanie: czy istnieje sposób, w jaki mogę przekonać moje przesłonięcie oparte na SFINAE, aby zignorować "samą siebie", decydując, czy powinno zawieść, czy nie, czy jakoś pozbyć się w jakiś sposób problemu samo-referencyjnego? Lub obniżyć pierwszeństwo mojego operator!= wystarczająco niskie, aby wygrać jednoznaczne !=, nawet jeśli w przeciwnym razie nie jest tak dobre dopasowanie?

Ten, który nie sprawdza, czy "!= nie istnieje" działa dość dobrze, ale nie na tyle dobrze, bym był tak niegrzeczny, jak wstrzyknąć go do globalnej przestrzeni nazw.

Celem jest każdy kod, który skompilowałby się bez mojej "magii". != robi dokładnie to samo, gdy moja "magia" zostanie wprowadzona w postaci !=. Jeśli i tylko wtedy, gdy != jest w inny sposób nieważny, ibool r = !(a==b) jest dobrze uformowany, jeśli moja "magia" != zostanie wciągnięta.


Przypis : Jeśli utworzyć template<typename U, typename T> bool operator!=(U&& u, T&& t), SFINAE pomyśli, że każda para typów posiada ważne != między nimi. Następnie, gdy próbujesz wywołać !=, jest on tworzony i nie można go skompilować. Oprócz tego możesz stompować funkcje bool operator!=(const foo&, const foo&), ponieważ lepiej pasujesz do foo() != foo() i foo a, b; a != b;. Rozważam robienie obu tych niegrzecznych.

+0

Jestem pewien, że jesteś świadomy, ale 'namespace std :: rel_ops' z' 'ma praktyczną (i naiwną) wersję tego. –

+2

Zamień domyślny argument szablonu dla parametru innego niż typ i podaj gdzieś kod SSCCE, który można łatwo skopiować. – Xeo

+1

+1 tylko za robienie szalonych rzeczy. Rekurencyjne SFINAE ... Wow :-P –

Odpowiedz

11

Problem z twoim podejściem wydaje się, że awaryjna globalna definicja operator != jest zbyt atrakcyjna i potrzebujesz kontroli SFINAE, aby ją wykluczyć. Jednak kontrola SFINAE zależy od kwalifikowalności samej funkcji do rozdzielczości przeciążenia, co prowadzi do (próby) nieskończonej rekurencji podczas dedukcji typu.

Wydaje mi się, że jakakolwiek podobna próba bazująca na SFINAE uderzy w tę samą ścianę, więc moim zdaniem najbardziej rozsądnym podejściem jest, aby twoje operator != było mniej atrakcyjne dla rozdzielczości przeciążania, i niech inne , racjonalnie napisane (to będzie jasne za chwilę) pierwszeństwo mają przeciążenia operator !=.

Biorąc pod uwagę rodzaj cecha can_equal podałeś:

#include <type_traits> 
#include <functional> 

template<typename T, typename U, typename=void> 
struct can_equal : std::false_type {}; 

template<typename T, typename U> 
struct can_equal< 
    T, 
    U, 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>() == std::declval<U>()), 
     bool 
     >::value 
    >::type 
>: std::true_type {}; 

chciałbym zdefiniować awaryjnej operator != ten sposób:

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

template< 
    typename T, 
    typename... Ts, 
    typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr 
    > 
bool operator != (T const& t, Ts const&... args) 
{ 
    return is_not_equal(t, args...); 
} 

O ile mi wiadomo, każdy przeciążenie operator != który określi dokładnie dwa Parametry funkcji (więc bez pakietu argumentów) będą lepiej pasować do rozdzielczości przeciążania. Dlatego powyższa, zastępcza wersja operator != zostanie wybrana tylko wtedy, gdy nie będzie lepszej przeciążalności. Co więcej, zostanie wybrana tylko wtedy, gdy cecha typu can_equal<> zwróci true.

Przetestowałem ten przeciwko SSCCE jesteś przygotowany, gdzie cztery struct s są zdefiniowane razem z niektórymi przeciążeń operator == i operator !=:

struct test { }; 

bool operator==(const test&, const test&) { std::cout << "(==)"; return true; } 
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; } 

struct test2 { }; 

struct test3 { }; 
bool operator == (const test3&, const test3&) 
{ std::cout << "(==)"; return true; } 

struct test4 { }; 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator == (T&&, T&&) { std::cout << "(==)"; return true; } 

template<typename T, 
     EnableIf< std::is_convertible< T, test4 const& >::value >... > 
bool operator != (T&&, T&&) { std::cout << "(!=)"; return true; } 

aby sprawdzić, czy sygnał wyjściowy jest produkowany i lustro, co zrobiłeś w swojej pierwotnej wersji awaryjnej operator != dodałem wydruk do is_not_equal():

template<typename T, typename U> 
bool is_not_equal(T&& t, U&& u) 
{ 
    std::cout << "!"; // <== FOR TESTING PURPOSES 
    return !(std::forward<T>(t) == std::forward<U>(u)); 
} 

Oto trzy testy ze swoim ex obszerna:

std::cout << (a != b) << "\n"; // #1 
std::cout << (test3() != test3()) << "\n"; // #2 
std::cout << (test4() != test4()) << "\n"; // #3 

Jeśli chodzi o pierwszy test, operator != jest zdefiniowane dla typu test, więc linia #1 należy wydrukować:

(!==)1 

Jeśli chodzi o drugi test, operator != jest nie zdefiniowane dla test3 i test3 nie można zmienić na test4, więc nasz globalny operator != powinien wejść w grę i zanegować wynik przeciążenia operator ==, który wymaga dwóch const test3&.Dlatego linia #2 należy wydrukować:

!(==)0 // operator == returns true, and is_not_equal() negates it 

Wreszcie trzeci Test składa się z dwóch rvalue obiektów typu test4, dla których operator != określone (bo argumenty są wymienialne na test4 const&). Dlatego linia #3 należy wydrukować:

(!=)1 

A oto live example pokazując, że produkcja wytwarzana jest oczekiwany jeden.

+0

+1, znacznie lepiej niż moje podejście. – ipc

+0

@ipc: Dziękuję, ale nadal nie wydaje się, aby dać taki sam wynik, jak w przypadku testu PO. A może źle to zinterpretuję. Na marginesie, SSCCE dostarczone przez OP daje różne wyniki z Clang i GCC, więc zastanawiam się, który kompilator zaufać –

+0

Dziwne jest to, że wydaje się to legalne z '13,5/1', ponieważ aa parametr funkcji pakiet liczy się jako dokładnie jeden parametr funkcji. Nie jestem pewien, czy jest to zgodne z normą. – ipc

Powiązane problemy