2016-07-09 7 views
7

mam niewiarygodnie ekscytujące bibliotekę, która może przełożyć punktów: powinien działać ze wszystkimi rodzajami punktowychJak rozwiązać dwuznaczność w przeciążonych funkcji wykorzystujących SFINAE

template<class T> 
auto translate_point(T &p, int x, int y) -> decltype(p.x, p.y, void()) 
{ 
    p.x += x; 
    p.y += y; 
} 

template<class T> 
auto translate_point(T &p, int x, int y) -> decltype(p[0], void()) 
{ 
    p[0] += x; 
    p[1] += y; 
} 

translate_point będzie współpracować z punktami, które mają publiczne x i y członków, i będzie również działać z krotkami/zmiennymi pojemnikami, gdzie x i y są reprezentowane odpowiednio przez pierwszy i drugi element.

Problemem jest inna biblioteka definiuje klasę punkt o publicznym x i y, ale również umożliwia indeksowanie:

struct StupidPoint 
{ 
    int x, y; 

    int operator[](int i) const 
    { 
     if(i == 0) return x; 
     else if(i == 1) return y; 
     else throw "you're terrible"; 
    } 

}; 

Moja aplikacja, za pomocą obu bibliotek, jest następujący:

int main(int argc, char **argv) 
{ 
    StupidPoint stupid { 8, 3 }; 
    translate_point(stupid, 5, 2); 
    return EXIT_SUCCESS; 
} 

ale to powoduje, że GCC (i klang) są nieszczęśliwe:

error: call of overloaded ‘translate_point(StupidPoint&, int, int)’ is ambiguous 

Teraz widzę, dlaczego tak się dzieje, ale chcę wiedzieć, jak to naprawić (zakładając, że nie mogę zmienić wewnętrznych ustawień StupidPoint), a jeśli nie ma łatwego rozwiązania, to jak mogę jako wykonawca biblioteki zrobić to łatwiej się z tym uporać.

+3

W którym przypadku chcesz zadzwonić? –

+0

W tym przypadku pierwsza wersja, ponieważ jest (przynajmniej teoretycznie) nieco szybsza. Również 'operator []' ma również niestałe przeciążenie zwracające 'int &', które pominąłem w moim prostym przypadku testowym. – jaymmer

Odpowiedz

3

Jeśli chcesz dać pierwszeństwo przypadku mającej publicznego x/y, można to zrobić:

template<class T> 
auto translate_point_impl(int, T &p, int x, int y) -> decltype(p.x, p.y, void()) 
{ 
    p.x += x; 
    p.y += y; 
} 

template<class T> 
auto translate_point_impl(char, T &p, int x, int y) -> decltype(p[0], void()) 
{ 
    p[0] += x; 
    p[1] += y; 
} 

template<class T> 
void translate_point(T &p, int x, int y) { 
    translate_point_impl(0, p, x, y); 
} 

Jest rzeczą oczywistą, że naprzeciwko konfiguracja jest przez przełączanie rodzajów pierwszego parametru.


Jeśli masz trzy lub więcej opcji (mówi N), można użyć trick w oparciu o szablony.
Oto przykład powyżej raz włączony do takiej struktury:

template<std::size_t N> 
struct choice: choice<N-1> {}; 

template<> 
struct choice<0> {}; 

template<class T> 
auto translate_point_impl(choice<1>, T &p, int x, int y) -> decltype(p.x, p.y, void()) { 
    p.x += x; p.y += y; 
} 

template<class T> 
auto translate_point_impl(choice<0>, T &p, int x, int y) -> decltype(p[0], void()) { 
    p[0] += x; 
    p[1] += y; 
} 

template<class T> 
void translate_point(T &p, int x, int y) { 
    // use choice<N> as first argument 
    translate_point_impl(choice<1>{}, p, x, y); 
} 

Jak widać, teraz N może przyjąć dowolną wartość.

+0

to jest rzeczywiście bardzo sprytny, ale czy istnieje sposób, aby to działało, jeśli masz 3 (lub więcej) opcji do wyboru? – jaymmer

+0

Nigdy wcześniej nie widziałem tego 'decltype (void())', to jest miłe. To jest dla mnie dziwne, że tutaj "domyślne konstruowanie pustki"? Fajnie, że to działa. –

+0

@jaymmer Tak, możesz użyć klasy szablonu, aby rozwiązać problem przeciążenia. Czy chcesz, abym dodał przykład do odpowiedzi? – skypjack

7

Można dostarczyć przeciążenie dla StupidPoint:

auto translate_point(StupidPoint &p, int x, int y) 
{ 
    p.x += x; 
    p.y += y; 
} 

live example


Innym rozwiązaniem:

Od operator[] jest const dla StupidPoint, można sprawdzić to w swoim SFINAE warunek:

template<class T> 
auto translate_point(T &p, int x, int y) -> decltype(p[0] += 0, void()) 
{ 
    p[0] += x; 
    p[1] += y; 
} 

live example


Można również użyć innego typu cech podejścia opartego aby wybrać odpowiedni translate_point funkcję:

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

template<typename T> 
struct has_x_y<T, decltype(std::declval<T>().x, std::declval<T>().y, void())> : std::true_type { }; 

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

template<typename T> 
struct has_index<T, decltype(std::declval<T>().operator[](0), void())> : std::true_type { }; 

template<class T> 
std::enable_if_t<has_x_y<T>::value> translate_point(T &p, int x, int y) 
{ 
    p.x += x; 
    p.y += y; 
} 

template<class T> 
std::enable_if_t<!has_x_y<T>::value && has_index<T>::value> translate_point(T &p, int x, int y) 
{ 
    p[0] += x; 
    p[1] += y; 
} 

live example

1

Z SFINAE, chciałbym zrobić coś takiego:

template<class T, bool> 
auto translate_point(T &p, int x, int y) -> decltype(p[0], void()) 
{ 
    p[0] += x; 
    p[1] += y; 
} 

template<class T, bool = std::is_base_of<StupidPoint, T>::value> 
auto translate_point(T &p, int x, int y) -> decltype(p.x, p.y, void()) 
{ 
    p.x += x; 
    p.y += y; 
} 

W ten sposób, gdy T = (klasa z StupidPoint jako klasy bazowej), drugi przeciążenie zostanie wywołana.

Ale łatwiej z prostym przeciążeniem, na co wskazuje m.s.

2

W tym przypadku oba przeciążenia są niewystarczające.Naprawdę nie można nazwać drugiego z StupidPoint, ale nie jest to możliwe do zaobserwowania w punkcie, w którym występuje przeciążenie. Jeśli ograniczyć zarówno poprawnie, musisz usunąć niejasności w tym przypadku:

template<class T> 
auto translate_point(T &p, int x, int y) -> decltype(p.x += x, p.y += y, void()) { ... }; 

template<class T> 
auto translate_point(T &p, int x, int y) -> decltype(p[1] += y, void()) { ... } // prefer checking 1, so you don't allow an operator[] that takes a pointer 

Teraz, jeśli operator[] zwrócony int& zamiast tego nadal będzie niejednoznaczna. W takim przypadku potrzebujesz sposobu na zamówienie dwóch przeciążeń (może z dodatkowym argumentem, który jest albo int lub ...?), Albo po prostu nie dopuszczaj do tego przypadku. To osobna decyzja projektowa.

Powiązane problemy