2016-09-14 11 views
5

Rozważmy następujący kod:C++ częściowe uporządkowanie dla funkcji z domyślnym argumentem

template <class...> 
using void_t = void; 

template <class T> 
void bar(T){} 

template <class T> 
void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0) {} 

struct A { void foo(); } a; 
bar(a); // gives a compiler error on ambiguous call 

Więc pytanie jest, dlaczego są te przeciążenia niejednoznaczne? Dlaczego drugie przeciążenie nie jest uważane za bardziej restrykcyjne, bardziej wyspecjalizowane niż drugie przez kompilator?

+4

* Co sprawia, że ​​jest bardziej wyspecjalizowana *? Obie wersje można wywoływać bez konwersji, dlatego nie wiadomo, do którego połączenia. – NathanOliver

+2

Być może warto wspomnieć, że w momencie, gdy mamy do czynienia z przeciążeniem, nie dbamy już o to, co jest bardziej wyspecjalizowane. odbywa się to w innej fazie kompilacji. –

+0

@RichardHodges Rozdzielczość przeciążenia jest dokładnie tam, gdzie myślisz o częściowym zamówieniu. –

Odpowiedz

2

Jednym ze zwyczajowych sposobów, aby to zrobić, jest stworzenie alternatywy dla wyspecjalizowanych szablonów.

#include <utility> 
#include <iostream> 

template<class T> 
struct has_foo_impl 
{ 
    template<class U> static auto test(U* p) -> decltype(p->foo(), void(), std::true_type()); 
    static auto test(...) -> decltype(std::false_type()); 

    using type = decltype(test((T*)nullptr)); 
}; 

template<class T> using has_foo = typename has_foo_impl<T>::type; 

template <class...> 
using void_t = void; 

template <class T, std::enable_if_t<not has_foo<T>::value>* = nullptr> 
void bar(T){ 
    std::cout << "not me\n"; 
} 

template <class T, std::enable_if_t<has_foo<T>::value>* = nullptr> 
void bar(T) { 
    std::cout << "me\n"; 
} 

struct A { void foo(); } a; 

int main() 
{ 
    bar(a); // "me" 
} 
-1

Ponieważ używasz wartości domyślnej dla przeciążonej funkcji, więc kompilator nie wie, której funkcji chcesz użyć.

void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0) 

bez

= 0 

to działa dobrze.

/EDIT

template <class...> 
using void_t = void; 

template <class T> 
void bar(T) 
{} 

template <class T> 
void bar(T, void_t<decltype(std::declval<T>().foo())>*) 
{} 

struct A 
{ 
    void foo(); 
} a; 

int main() 
{ 
    bar(a); 
    return 0; 
} 

To działa.

The program '[7464] Project3.exe' has exited with code 0 (0x0). 
+0

dlaczego to działa dobrze? Nie mogę dłużej wywoływać paska z pojedynczym argumentem i to właśnie potrzebuję. – Bikineev

+1

Po edycji dodano następujący komentarz: na pewno działa, ale wywołuje pierwsze przeciążenie, a nie drugie, zgodnie z zamierzeniami – Bikineev

3

Próbujesz użyć SFINAE wymusić wybór konkretnego kandydata w konkretnym przypadku (tu obecność na żądanie podmiotu foo sobą nie argumentu wewnątrz T lwartości). Co tak naprawdę się tutaj dzieje?

Szablon z void_t jest dobrze zdefiniowany dla twojego struct A, więc w momencie połączenia masz dwóch ważnych kandydatów do uzyskania rozdzielczości przeciążenia. Jeśli chcesz korzystać z SFINAE, musisz upewnić się, że dla danego połączenia dostępna jest tylko jedna przeciążenie. Aby to zrobić, powinieneś najpierw osadzić swój test w typie. W tym celu można wziąć przykład na Yakk's can_apply facility które bezczelnie kopiować tutaj, ponieważ bardzo dobrze pasuje do Państwa potrzeb:

namespace details { 
    // if Z<Ts...> is invalid, false_type: 
    template <template<class...> class Z, class always_void, class... Ts> 
    struct can_apply : std::false_type {}; 

    // if Z<Ts...> is valid, true_type: 
    template <template<class...> class Z, class... Ts> 
    struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type {}; 
} 
// alias to inject the void type where we need it for SFINAE: 
template <template<class...> class Z, class... Ts> 
using can_apply = details::can_apply<Z, void, Ts...>; 

template <typename T> 
using has_foo_t = decltype(std::declval<T>().foo()); 

template <typename T> 
using has_foo = can_apply<has_foo_t, T>; 

teraz mamy tylko do korzystania cechę określoną powyżej w naszych definicji szablonu:

// The enable_if with the negation is needed to invalidate 
// this implementation when T indeed has foo(). 
// This is what you were missing in your original idea. 
template <typename T> 
std::enable_if_t<!has_foo<T>::value> bar(T) { 
    std::cout << "T has no foo(void)" << std::endl; 
} 

template <typename T> 
std::enable_if_t<has_foo<T>::value> bar(T) { 
    std::cout << "T has a foo(void)" << std::endl; 
} 

Możesz zobaczyć działający przykład na Coliru.

+0

tak samo jak ja pod wrażeniem can_apply, uważam, że to trochę głupi umysł ... :) –

+0

can_apply to świetna, fajna odpowiedź, dzięki – Bikineev

+1

@Bikineev Zwróć uwagę, że podobny mechanizm nazywa się "" is_detected' "] (http: // en .cppreference.com/w/cpp/experimental/is_detected) znajduje się w ścieżce standardów. Zawiera kilka dodatkowych rzeczy wraz z 'can_apply'. – Yakk

1

Żadna z funkcji nie jest bardziej wyspecjalizowana niż inna.

W każdym razie, można siła wybór za pomocą dwóch przeciążone funkcje, jak następuje:

#include<utility> 

template <class T> 
void bar(char, T){} 

template <class T> 
auto bar(int, T) -> decltype(std::declval<T>().foo(), void()) {} 

struct A { void foo(); } a; 

int main() { 
    bar(0, a); 
} 

Dla 0 jest int, bar(int, T) jest sądzony za pierwszym. Z powodu sfinae, może on zostać odrzucony, jeśli T nie ma funkcji składowej . W każdym razie, 0 może być rzutowany na char, w ten sposób wybiera się bar(char, T).
Jeśli zdarza się, że obie funkcje są wykonalne, wywoływana jest bar(int, T), ponieważ nie wymaga żadnego niejawnego rzutowania, a wywołanie nie jest już niejednoznaczne.

1

jest inne rozwiązanie, które zestawia z g ++:

//g++ 4.9.3 

#include <iostream> 

template <class T> 
    void bar(T) 
{std::cout << "not foo boo...\n";} 

template <class T > 
    void bar(decltype(std::declval<T>().foo(),T())) 
{std::cout << "foo\n";} 

template <class T > 
    void bar(decltype(std::declval<T>().boo(),T())) 
{std::cout << "boo\n";} 

struct Foo { void foo(); } f; 

struct Boo {void boo(); } b; 

struct NotFooBoo { } n; 
template <class T> 
    auto func(T&& t) 
{ 
    return bar<typename std::decay<T>::type>(std::forward<T>(t)); 
} 
int main() 
{ 
    func(f); 
    func(b); 
    func(n); 
} 
+0

Tę samą technikę prezentuje skypjack. A co, gdybym zadzwonił do paska z argumentem 'struct BothFooBoo: public Foo, Boo {};'? – Rerito

+0

@Rerito Nie widzę żadnego podobieństwa ze skypjakiem, oprócz tego, że oba używane typy decltype z dwoma wyrażeniami oddzielonymi przecinkiem! odpowiedź opiera się na założeniach pytań mających na celu rozróżnienie klas, które mają foo i inne zamiast mieć obie funkcje. możesz zadać pytanie jako nowy wątek, aby uzyskać właściwą odpowiedź :) – rahnema1

+0

Tak, on umieścił go w zwrotnym typie zwracania, ale wciąż jest taki sam. Jeśli chodzi o mój drugi komentarz, właśnie wskazywałem na wadę projektu w twojej odpowiedzi: typ, który ma zarówno foo, jak i boo, dałby niejednoznaczne połączenie. – Rerito

Powiązane problemy