2015-12-30 11 views
14

Mały przegląd. Piszę szablon klasy, który zapewnia silny typedef; silnym typedef kontrastuję ze zwykłym typedef, który właśnie deklaruje alias. Aby dać pomysł:Dlaczego operatory porównania std :: vector i std :: string są zdefiniowane jako funkcje szablonu?

using EmployeeId = StrongTypedef<int>; 

Teraz istnieją różne szkoły myślenia na temat silnych typedefs i niejawnych konwersji. Jedna z tych szkół mówi: nie każda liczba całkowita jest EmployeeId, ale każdy EmployeeId jest liczbą całkowitą, więc powinieneś zezwolić na niejawne konwersje z EmployeeId na liczbę całkowitą. I można zaimplementować to i pisać takie rzeczy jak:

EmployeeId x(4); 
assert(x == 4); 

To działa, ponieważ x dostaje niejawnie konwertowane na liczbę całkowitą, a następnie porównanie całkowitą równość jest używany. Jak na razie dobrze. Teraz chcę zrobić to z wektorem liczb całkowitych:

using EmployeeScores = StrongTypedef<std::vector<int>>; 

Więc mogę robić takie rzeczy:

std::vector<int> v1{1,2}; 
EmployeeScores e(v1); 
std::vector<int> v2(e); // implicit conversion 
assert(v1 == v2); 

Ale nadal nie mogę tego zrobić:

assert(v1 == e); 

Powód, dla którego to nie działa, wynika z tego, jak std::vector definiuje kontrolę równości, zasadniczo (modulo standardese):

template <class T, class A> 
bool operator==(const vector<T,A> & v1, const vector<T,A> & v2) { 
... 
} 

To jest szablon funkcji; ponieważ zostanie odrzucony we wcześniejszej fazie wyszukiwania, nie pozwoli na porównanie typu, który konwertuje się niejawnie na wektor.

Innym sposobem definiowania równości byłoby tak:

template <class T, class A = std::allocator<T>> 
class vector { 
... // body 

    friend bool operator==(const vector & v1, const vector & v2) { 
    ... 
} 

} // end of class vector 

W tym drugim przypadku, operator równości nie jest szablonem funkcji, to po prostu zwykła funkcja, która jest generowana wraz z klasą, podobny do funkcja członka. Jest to nietypowy przypadek włączony przez słowo kluczowe przyjaciel.

Pytanie (przepraszam, że tło było tak długie), dlaczego zamiast numeru std::vector użyć drugiego formularza zamiast pierwszego? To sprawia, że ​​vector zachowuje się bardziej jak prymitywne typy, a jak widać wyraźnie pomaga w moim przypadku użycia. To zachowanie jest jeszcze bardziej zaskakujące w przypadku string, ponieważ łatwo zapomnieć, że string jest po prostu typedef szablonu klasy.

Dwie rzeczy, które rozważyłem: po pierwsze, niektórzy mogą sądzić, że ponieważ funkcja przyjaciół zostanie wygenerowana z klasą, spowoduje to ciężką awarię, jeśli zawarty typ wektora nie obsługuje porównania równości. Nie o to chodzi; podobnie jak funkcje składowe klas szablonów, nie są generowane, jeśli nie są używane. Po drugie, w bardziej ogólnym przypadku wolne funkcje mają tę zaletę, że nie muszą być zdefiniowane w tym samym nagłówku, co klasa, co może mieć zalety. Ale to wyraźnie nie jest tutaj wykorzystywane.

Co daje? Czy istnieje ku temu dobry powód, czy był to po prostu nieoptymalny wybór?

Edytuj: Napisałem krótki przykład demonstrujący dwie rzeczy: zarówno ta niejawna konwersja działa zgodnie z życzeniem z podejściem przyjacielskim, jak i to, że nie występują żadne poważne awarie, jeśli szablonowy typ nie spełnia wymagań operatora równości (oczywiście zakładając, że operator równości nie jest używany w tym przypadku).Edytuj: poprawiono, aby przeciwstawić się pierwszemu podejściu: http://coliru.stacked-crooked.com/a/6f8910945f4ed346.

+1

dlaczego to zostało odrzucone? – Untitled123

+1

Czy jesteś pewien, że z 'friend'version,' assert (v1 == e); 'skompilowałoby? – YSC

+0

@YSC Tak, dodałem link do Coliru, daj mi znać, jeśli w pełni odpowiada on na twoje pytanie. –

Odpowiedz

0

EDIT: Po tym, jak ponownie przeczytać moje wyjaśnienie, pod wpływem kilku uwag wokół, jestem przekonany, mój oryginalny rozumowanie nie jest rzeczywiście przekonujące. Moja odpowiedź zasadniczo miała na celu stwierdzenie, że chociaż wartość x może być niejawnie przekształcona w wartość y innego typu, nie musi być koniecznie "automatycznej" porównania równości między tymi dwoma. Dla kontekstualizacji pozostawiam tutaj kod, którego użyłem jako przykładu.

struct B {}; 

template <class T> 
struct A { 
    A() {} 
    A(B) {} 
    friend bool operator==(const A<T>&, const A<T>&) { return false; } 
}; 

// The template version wouldn't allow this to happen. 
// template <class T> 
// bool operator==(const A<T>&, const A<T>&) { return false; } 

int main() { 
    A<B> x; 
    B y; 
    if (x == y) {} //compiles fine 
    return 0; 
} 
+0

'A' twierdzi, że jest niejawnie konwertowalne - z' B'. Lepsza wersja ma 'B', która ma' operatora A', ale w obu przypadkach niezmienniki niezawodnie najczęściej oznaczają, że kod konwersji jest zły. Większym problemem dla mnie byłyby koszty, ale nawet wtedy kosztowne niejawne konwersje są również zapachem kodu. – Yakk

+0

Dając A nie jawny konstruktor z B, zgodziłeś się, że wszystko, co akceptuje A, również zaakceptuje B. Nie ma nic szczególnego w tym operatorze porównania. Również w kategoriach standardu A przyjmuje rolę wektora, a wektor nie ma takich konstruktorów. –

+0

Problem widoczności jest obecny, ale może on mieć znaczenie tylko wtedy, gdy faktycznie wywołujesz z operatorem składni == (...). Więc zmiana tego byłaby niezgodna wstecz. Ale nikt tak naprawdę nie chce tego robić, więc nie wyjaśnia, dlaczego tak jest na pierwszym miejscu. –

2

opisać techique (co nazywam operatorów Koenig) nie była znana, przynajmniej nie w szerokim zakresie, w punkcie vector został zaprojektowany i oryginalnie podane.

Zmiana teraz wymagałaby większej ostrożności niż początkowo używania i większego usprawiedliwienia.

Jak się domyślamy, dzisiaj operatorzy Koeniga będą wykorzystywani zamiast szablonów.

+0

"Domyślam się, że operatorzy Koeniga mieliby być używane zamiast operatorów szablonów." Wątpię. 'basic_string_view' dodał zamiast tego" wystarczające dodatkowe przeciążenia ", aby obsłużyć niejawne konwersje. –

Powiązane problemy