Co to jest std :: decay i kiedy należy go stosować?

185

Jakie są powody istnienia std::decay? W jakich sytuacjach std::decayprzydaje się?

Eric Javier Hernandez Saura
źródło
3
Jest używany w standardowej bibliotece, np. Podczas przekazywania argumentów do wątku. Te muszą być przechowywane według wartości, abyś nie mógł przechowywać np. Tablic. Zamiast tego wskaźnik jest zapisywany i tak dalej. Jest to również metafunkcja naśladująca regulacje typu parametru funkcji.
wtorek
3
decay_t<decltype(...)>to fajna kombinacja, aby zobaczyć, co autoby to wydedukować.
Marc Glisse,
58
Zmienne radioaktywne? :)
saiarcot895
7
std :: decay () może robić trzy rzeczy. 1 Potrafi przekonwertować tablicę T na T *; 2. Może usunąć kwalifikator cv i odniesienie; 3. Konwertuje funkcję T na T *. np. rozpad (void (char)) -> void (*) (char). Wydaje się, że nikt nie wspomniał o trzecim użyciu w odpowiedziach.
r0ng
1
Dzięki Bogu, nie mamy jeszcze kwarków w c ++
Wormer

Odpowiedzi:

192

<joke> Oczywiście służy do rozkładania std::atomictypów radioaktywnych na nieradioaktywne. </joke>

N2609 to papier, który zaproponował std::decay. Artykuł wyjaśnia:

Mówiąc najprościej, decay<T>::typeto transformacja typu tożsamości, chyba że T jest typem tablicy lub odniesieniem do typu funkcji. W takich przypadkach decay<T>::typezwraca odpowiednio wskaźnik lub wskaźnik do funkcji.

Motywującym przykładem jest C ++ 03 std::make_pair:

template <class T1, class T2> 
inline pair<T1,T2> make_pair(T1 x, T2 y)
{ 
    return pair<T1,T2>(x, y); 
}

który zaakceptował jego parametry według wartości, aby literały łańcuchowe działały:

std::pair<std::string, int> p = make_pair("foo", 0);

Jeśli zaakceptuje swoje parametry przez odniesienie, to T1zostanie wydedukowane jako typ tablicy, a następnie zbudowanie a pair<T1, T2>będzie źle sformułowane.

Ale oczywiście prowadzi to do znacznej nieefektywności. Stąd potrzeba decayzastosowania zestawu transformacji, które występują, gdy wystąpi przekazanie przez wartość, co pozwala uzyskać efektywność pobierania parametrów przez odniesienie, ale nadal uzyskać transformacje typu potrzebne do działania kodu z literałami ciągów, typy tablic, typy funkcji i tym podobne:

template <class T1, class T2> 
inline pair< typename decay<T1>::type, typename decay<T2>::type > 
make_pair(T1&& x, T2&& y)
{ 
    return pair< typename decay<T1>::type, 
                 typename decay<T2>::type >(std::forward<T1>(x), 
                                            std::forward<T2>(y)); 
}

Uwaga: nie jest to rzeczywista make_pairimplementacja C ++ 11 - C ++ 11 make_pairrównież rozpakowuje std::reference_wrappers.

TC
źródło
„T1 zostanie wydedukowane jako typ tablicy, a następnie zbudowanie pary <T1, T2> będzie źle sformułowane”. Jaki jest tutaj problem?
camino
6
Rozumiem, w ten sposób otrzymamy parę <char [4], int>, która może akceptować tylko ciągi znaków z 4 znakami
camino
@camino Nie rozumiem, czy mówisz, że bez std :: decay pierwsza część pary zajmowałaby 4 bajty dla czterech znaków zamiast jednego wskaźnika do char? Czy to właśnie robi std :: forward? Zatrzymuje rozkładanie się z tablicy na wskaźnik?
Zebrafish,
3
@Zebrafish To rozpad tablicy. Na przykład: szablon <typename T> void f (T &); f („abc”); T jest char (&) [4], ale szablon <typename T> void f (T); f („abc”); T oznacza char *; Możesz również znaleźć wyjaśnienie tutaj: stackoverflow.com/questions/7797839/…
camino
69

W przypadku funkcji szablonu, które pobierają parametry typu szablonu, często masz parametry uniwersalne. Parametry uniwersalne są prawie zawsze referencjami takiego czy innego rodzaju. Są również wykwalifikowani. W związku z tym większość cech typu nie działa na nich tak, jak można się spodziewać:

template<class T>
void func(T&& param) {
    if (std::is_same<T,int>::value) 
        std::cout << "param is an int\n";
    else 
        std::cout << "param is not an int\n";
}

int main() {
    int three = 3;
    func(three);  //prints "param is not an int"!!!!
}

http://coliru.stacked-crooked.com/a/24476e60bd906bed

Rozwiązaniem jest użycie std::decay:

template<class T>
void func(T&& param) {
    if (std::is_same<typename std::decay<T>::type,int>::value) 
        std::cout << "param is an int\n";
    else 
        std::cout << "param is not an int\n";
}

http://coliru.stacked-crooked.com/a/8cbd0119a28a18bd

Mucząca Kaczka
źródło
14
Nie jestem z tego zadowolony. decayjest bardzo agresywny, np. po zastosowaniu do odwołania do tablicy daje wskaźnik. Zazwyczaj jest zbyt agresywny dla tego rodzaju metaprogramowania IMHO.
wt
@dyp, co jest mniej „agresywne”? Jakie są alternatywy?
Serge Rogatch
5
@SergeRogatch W przypadku „parametrów uniwersalnych” / referencji uniwersalnych / referencji przesyłania, po prostu remove_const_t< remove_reference_t<T> >ewentualnie zawinę w niestandardową metafunkcję.
wtorek
1
gdzie w ogóle używany jest param? Jest to argument func, ale nigdzie nie widzę, aby był używany
savram
2
@savram: W tych fragmentach kodu: tak nie jest. Sprawdzamy tylko typ, a nie wartość. Wszystko powinno działać dobrze, jeśli nie lepiej, jeśli usuniemy nazwę parametru.
Mooing Duck