2012-11-10 8 views
5
template <class T, class U> decltype(*(T*)(0) * *(U*)(0)) mul(T x, U y) { 
    return x * y; 
} 

Ten fragment kodu pochodzi z wersji C++11 FAQ firmy Stroustrup. Rozumiem, co robi, czyli mnożymy dwa obiekty różnych typów. Co mnie niepokoi, to składnia między parametrami szablonu a definicją funkcji. Co się dzieje wewnątrz decltype? Rozumiem, że jest to odwołanie do nienazwanego wskaźnika T inicjowanego na 0 i pomnożenie go przez nienazwany wskaźnik U, który został dereferencjonowany i zainicjowany w ten sam sposób. Czy mam rację?Mylące składnie z anonimowymi typami szablonów?

Cóż, jeśli tak się dzieje, to czy stosowanie wskaźników, dereferencji i dodatkowego nawiasu nie jest zbyteczne? Nie udało się zainicjować typy tak utrzymując pożądany efekt ?:

template <class T, class U> decltype(T(0) * U(0)) mul(T x, U y) { 
    return x * y; 
} 

wygląda to znacznie czystsze do mnie, a to robi mieć taki sam skutek, gdy iloczyn dwóch liczb jak w pierwszym ...

mul(4, 3); // 12 

Dlaczego więc Stroustrup kładzie nacisk na użycie złożonej składni wskaźnika, dereferencji i inicjalizacji? Jest to oczywiście przed wprowadzeniem nowej składni auto. Ale w każdym razie, moje pytanie jest: Czy istnieje jakaś różnica między dwoma powyższymi formami inicjowania typu? Gdzie używa wskaźników i natychmiast je dereferencje zamiast po prostu robić to, co zrobiłem, co było do inicjowania typów bez wskaźników lub dereferencji? Każda reakcja jest doceniana.

+0

Co stanie się, gdy otrzymamy 'T', który nie jest skonstruowany z' int'? –

Odpowiedz

6

Twoja wersja jest odpowiednikiem , a nie.

  1. Twoja wersja zakłada, że ​​zarówno T i U może być wykonana z 0. Oczywiście nie można oczekiwać tego od matryc, ale można je pomnożyć.
  2. T(0) daje tymczasowy (który może wiązać się z T&&), podczas gdy *(T*(0)) daje odniesienie do istniejącego obiektu (to jest T&), dlatego może zostać wybrany inny operator.

Jednak ani wersja Stroustrup, ani twoja nie są używane w praktyce. Na poziomie równoważnym realizacji kompilatora, należałoby użyć:

template <typename T, typename U> 
decltype(std::declval<T>() * std::declval<U>()) mul(T x, U y); 

Ale to nie podejmując zaletę Późno Return Type specyfikacji, zbudowany, aby umożliwić przesunięcie deklaracji typu zwrotnego po z funkcji deklaracji argumenty : auto f(int, int) -> int. Gdy argumenty zostaną zadeklarowane, mogą być użyte, co jest niesamowicie przydatne dla decltype!

template <typename T, typename U> 
auto mul(T x, U y) -> decltype(x * y); 

Ta ostatnia forma gwarantuje pobranie tego samego przeciążenia operatora niż treść funkcji, kosztem powtórzenia (niestety).

+0

Dlaczego nie musimy wstawiać 0 w parametrach 'std :: declval ()'? – 0x499602D2

+1

@David: nie ma argumentu dla 'declval', więc nie całkiem rozumiem twoje pytanie, przepraszam: x –

+0

Robisz' std :: declval () * std :: declval () 'ale jak czy wiemy, że to liczby? Gdzie mam 'T (0)' ja inicjuję go do 0; dlaczego nie mamy tego w parametrach "declval"? – 0x499602D2

5

Twoja wersja kodu zakłada, że ​​T i U mają domyślne konstruktory. Wersja Stroustrup tego nie robi, tworzy obojętny obiekt przez dereferencję pustego wskaźnika. Nie ma to oczywiście znaczenia, ponieważ kod ten nie jest przeznaczony do wykonania, należy go tylko przeanalizować, aby poznać wynikowy typ.

4

decltype treść jest nieocenionym kontekstem; nie ma znaczenia, co tam jest, o ile wynika z rodzaju. Pomyśl przez chwilę o T są zdefiniowane następująco:

struct T 
{ 
    int operator*(const U &) { return 2; } 
}; 

Nie ma konstruktora biorąc int lub dowolnego rodzaju, że int jest wymienialna na; dlatego T(0) nie powoduje obiektu, nawet w nieocenionym kontekście decltype. Dlatego używanie unevaluated zerowych referencji jest prawdopodobnie najłatwiejszym sposobem na uzyskanie odpowiedniego typu. Podsumowując: nie wiesz, co konstruktorzy mają T i U mieć, więc powinieneś użyć pustego odniesienia, które odwołuje się do fałszywego obiektu odpowiedniego typu.