2010-10-08 9 views
6

Ostatnie pytanie, które zadałem, było czymś, na co natknąłem się, próbując zrozumieć inną rzecz ... której również nie mogę zrozumieć (nie mój dzień).Niejawna konwersja nie dzieje się

To dość długie pytanie, ale przynajmniej mam nadzieję, że to pytanie może okazać się przydatne dla wielu osób, a nie tylko dla mnie.

kod mam jest następujący:

template <typename T> class V; 
template <typename T> class S; 

template <typename T> 
class V 
{ 
public: 
T x; 

explicit V(const T & _x) 
:x(_x){} 

V(const S<T> & s) 
:x(s.x){} 
}; 

template <typename T> 
class S 
{ 
public: 
T &x; 

explicit S(V<T> & v) 
:x(v.x) 
{} 
}; 

template <typename T> 
V<T> operator+(const V<T> & a, const V<T> & b) 
{ 
return V<T>(a.x + b.x); 
} 

int main() 
{ 
V<float> a(1); 
V<float> b(2); 
S<float> c(b); 

b = a + V<float>(c); // 1 -- compiles 
b = a + c;   // 2 -- fails 
b = c;    // 3 -- compiles 

return 0; 
} 

Ekspresja 1 i 3 działa idealnie, podczas gdy ekspresja 2 nie kompilacji.

Jeśli prawidłowo zrozumiane, co dzieje:

Ekspresja 1

  1. C jest niejawnie przekształca się const stosując standardową sekwencję konwersji (składający się tylko na jednej kwalifikacji konwersja). Wygenerowano
  2. V<float>(const S<T> & s) i tymczasowo wygenerowano obiekt const V<float> (nazwijmy go: t). Jest już ograniczony, ponieważ jest wartością czasową.
  3. a jest konwertowany na ciąg podobny do c. Wygenerowano
  4. operator+(const V<float> & a, const V<float> & b), co daje tymczasowy typ const V<float>, który możemy nazwać q.
  5. domyślnie wywoływana jest V<float>::operator=(const & V<float>).

Czy jestem OK do tego miejsca? Jeśli zrobiłem nawet najbardziej subtelny błąd, proszę, daj mi znać, bo staram się uzyskać wiedzę na temat odlewania niejawny jak najgłębiej ...

Expression 3

  1. c jest przekształcone na V<float>. W tym celu mamy zdefiniowaną przez użytkownika sekwencję konwersji:
    1.1. pierwsza standardowa konwersja: S<float> do const S<float> poprzez konwersję kwalifikacji.
    1.2. konwersja zdefiniowana przez użytkownika: const S<float> na V<float> przez konstruktor .
    1.3 druga standardowa konwersja: V<float> na const V<float> poprzez konwersję kwalifikacji.
  2. domyślnie wywoływana jest V<float>::operator=(const & V<float>).

Wyrażenie 2?

Nie rozumiem, dlaczego występuje problem z drugim wyrażeniem. Dlaczego następująca sekwencja nie jest możliwa?

  1. C przekształca się V<float>. W tym celu mamy zdefiniowaną przez użytkownika sekwencję konwersji:
    1.1. początkowa konwersja standard: S<float> do const S<float> poprzez konwersję kwalifikacji.
    1.2. konwersja zdefiniowana przez użytkownika: const S<float> na V<float> przez konstruktor .
    1.3. ostateczna konwersja standardowa: V<float> na const V<float> poprzez konwersję kwalifikacji.
  2. kroki od 2 do 6 są takie same jak w przypadku wyrażania 1.

Po przeczytaniu C++ Standard I chociaż: „hej! może problem musi z 13.3.3.1.2.3! który stanowi:

If the user-defined conversion is specified by a template conversion function, the second standard conversion sequence must have exact match rank.

Ale to nie może być przypadek, ponieważ kwalifikacja konwersja ma dokładnego dopasowania rangę ...

naprawdę nie mam pojęcia ...

Cóż, czy masz odpowiedź lub nie, dziękuję za przeczytanie tutaj :)

Odpowiedz

9

Jak zauważył Edric, konwersje nie są brane pod uwagę podczas dedukcji argumentów. Tutaj masz dwa konteksty gdzie parametr szablonu T można wywnioskować z rodzaju argumentów:

template<class T> 
v<T> operator+(V<T> const&, V<T> const&); 
       ~~~~~~~~~~~ ~~~~~~~~~~~~ 

Ale spróbować wywołać tę funkcję szablonu z V<float> po stronie lewej oraz S na prawa strona. Odliczenie argumentów w szablonie powoduje, że T = float dla lewej strony, a otrzymasz błąd dla prawej strony, ponieważ nie ma T, więc V<T> jest równy S<T>. To kwalifikuje się jako błąd odrzucenia szablonu szablonu, a szablon jest po prostu ignorowany.

Jeśli chcesz zezwolić na konwersje, operator + nie powinien być szablonem. Nie jest następujący trik: Można określić go jako przyjaciela inline wewnątrz szablonu klasy dla V:

template<class T> 
class V 
{ 
public: 
    V(); 
    V(S<T> const&); // <-- note: no explicit keyword here 

    friend V<T> operator+(V<T> const& lhs, V<T> const& rhs) { 
     ... 
    } 
}; 

ten sposób, operator nie jest już szablon.Nie ma więc potrzeby odliczania argumentów w szablonie, a twoje wywoływanie powinno działać. Operator znajduje się przez ADL (wyszukiwanie zależne od argumentu), ponieważ po lewej stronie znajduje się V<float>. Prawa strona jest również poprawnie przekonwertowana na V<float>.

Możliwe jest również wyłączenie odjęcia argumentu szablonu dla określonego argumentu. Na przykład:

template<class T> 
struct id {typedef T type;}; 

template<class T> 
T clip(
    typename id<T>::type min, 
    T value, 
    typename id<T>::type max) 
{ 
    if (value<min) value=min; 
    if (value>max) value=max; 
    return value; 
} 

int main() { 
    double x = 3.14; 
    double y = clip(1,x,3); // works, T=double 
} 

Chociaż typ pierwszy i ostatni argument jest typu int, nie są uwzględniane podczas szablonu odliczenia argumentu bo id<T>::type nie jest tzw * wywnioskować context`. Tak więc T jest wydedukowane tylko zgodnie z drugim argumentem, co skutkuje T = podwójnym bez żadnych sprzeczności.

+0

Dziękuję bardzo bardzo! Od początku patrzyłem na problem od początku ... dziękuję bardzo za sztuczkę "przyjaciela operatora", myślę, że właśnie to zrobię :) – jmeseguerdepaz

+0

dziękuję, dobrze. kolejny dowód na to, że C++ może być naprawdę brzydki i podły! – Atmocreations

+0

Możesz użyć 'std :: identity' w' 'zamiast' id' – David

0

Po prostu zgadnij, ale być może kompilator nie może odróżnić konwersji z V-> S lub z S-> V, próbując dowiedzieć się, jak dodać znak + c w wyrażeniu 2. Zakładasz, że kompilator będzie inteligentny eno Aby wybrać tę, która pozwala na kontynuowanie kompilacji z powodu reszty dostępnych funkcji, ale prawdopodobnie kompilator nie "czyta z wyprzedzeniem" (tak się mówi) i zaczyna się mylić z niejednoznacznością konwersji w górę przed próbą znaleźć operatora "+".

Oczywiście, jeśli dodano błąd kompilacji, to może pomóc wyjaśnić zbyt problem ...

+0

Cóż, zgodnie ze standardem operator + jest funkcją kandydującą, ponieważ ma poprawną liczbę argumentów. Zatem kompilator powinien być w stanie prześledzić konwersję, którą właśnie napisałem ... lub tak zrozumiałem. Jeśli chodzi o komunikat o błędzie: ../main.cpp:42: błąd: brak dopasowania dla 'operatora +' w 'a + c' (przepraszam, nie wiem, jak wstawić tutaj nowe linie) – jmeseguerdepaz

+0

Jak już powiedziałem, że wymagałoby kompilatora do "czytania z wyprzedzeniem" i konwertowania c przy użyciu konstruktora V, zanim był w stanie dopasować niestandardowy operator + określony. Ponieważ nie ma bezpośredniego operatora dopasowującego i nie ma ostatecznej konwersji w górę (która może być wewnętrzna, nie jest pewna), kompilator rezygnuje. I tak myślę o logice kompilatora. – Nick

+0

Wyjaśnienie: nie masz kandydata na operatora, ponieważ nie można utworzyć instancji szablonu, aby dopasować typy dodawane bez wywoływania niestandardowych operatorów konwersji. To moje zrozumienie. – Nick

4

Rozważając mecze szablonu, konwersje niejawne nie są używane. Dlatego w poniższym prosty przykład:

template < typename T > 
void foo(T t1, T t2) { /* do stuff */ } 

int main(int argc, char ** argv) { 
    foo(1, 1.0); 
    return 0; 
} 

To nie będzie skompilować chociaż jeden z argumentów może być niejawnie konwertowane do innego typu (int < -> double).

+0

Masz rację, pod warunkiem, że podany kod nie działa. Ale dlaczego? Nie widzę powodu w standardzie, a właściwie http://accu.org/index.php/articles/268 mówi "w większości przypadków szablon funkcji zachowuje się jak normalna funkcja przy rozważaniu rozdzielczości przeciążenia". I na koniec mamy rzecz, która niejawna konwersja ma miejsce w wyrażeniu "b = c" – jmeseguerdepaz

+0

Przy okazji, powodem, dla którego twój kod nie działa, jest problem z instancją, a nie niejawną konwersją. Jeśli wywołasz "foo" z foo (1, 1.0), nie ma problemu ze skompilowaniem. – jmeseguerdepaz

+0

Och, po przeczytaniu odpowiedzi sellibitze zdaję sobie sprawę, że prawdopodobnie próbowałeś mi powiedzieć, że ... Nie zrozumiałem, przepraszam. – jmeseguerdepaz

Powiązane problemy