Что такое std :: decay и когда его следует использовать?

194

Каковы причины существования std::decay? В каких ситуациях std::decayпригодится?

Эрик Хавьер Эрнандес Саура
источник
3
Он используется в стандартной библиотеке, например, при передаче аргументов потоку. Их нужно хранить по значению, поэтому вы не можете хранить, например, массивы. Вместо этого сохраняется указатель и так далее. Это также метафункция, которая имитирует настройку типа параметра функции.
dyp 08
3
decay_t<decltype(...)>хорошая комбинация, чтобы увидеть, что autoможно сделать.
Marc Glisse
61
Радиоактивные переменные? :)
saiarcot895 09
7
std :: decay () может делать три вещи. 1 Может преобразовывать массив T в T *; 2. Он может удалить квалификатор CV и ссылку; 3. Преобразует функцию T в T *. например, распад (void (char)) -> void (*) (char). Кажется, в ответах никто не упомянул о третьем использовании.
r0ng
1
Слава богу, у нас пока нет кварков в C ++
Вормер

Ответы:

200

<joke> Очевидно, он используется для распада радиоактивных std::atomicтипов на нерадиоактивные. </joke>

N2609 - это предложенная бумага std::decay. В документе объясняется:

Проще говоря, decay<T>::typeэто преобразование типа идентичности, кроме случаев, когда T является типом массива или ссылкой на тип функции. В этих случаях decay<T>::typeвозвращает указатель или указатель на функцию соответственно.

В качестве мотивирующего примера можно привести 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); 
}

который принимает параметры по значению, чтобы строковые литералы работали:

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

Если он принял свои параметры по ссылке, то T1будет выведен как тип массива, а затем построение pair<T1, T2>будет некорректным.

Но, очевидно, это приводит к значительной неэффективности. Следовательно, необходимо decayприменить набор преобразований, которые происходят при передаче по значению, что позволяет вам получить эффективность приема параметров по ссылке, но при этом получить преобразования типов, необходимые для работы вашего кода со строковыми литералами, типы массивов, типы функций и тому подобное:

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)); 
}

Примечание: это не реальная реализация C ++ 11 make_pair- C ++ 11 make_pairтакже разворачивает std::reference_wrappers.

TC
источник
«T1 будет выведен как тип массива, и тогда построение пары <T1, T2> будет некорректным». в чем проблема?
camino
7
Я понял, таким образом мы получим пару <char [4], int>, которая может принимать только строки с 4 символами
camino
@camino Я не понимаю, вы говорите, что без std :: decay первая часть пары будет занимать 4 байта для четырех символов вместо одного указателя на char? Это то, что делает std :: forward? Мешает ему распадаться от массива до указателя?
Zebrafish,
3
@Zebrafish Это распад массива. Например: template <typename T> void f (T &); f ("abc"); T - это char (&) [4], но шаблон <typename T> void f (T); f ("abc"); T - char *; Вы также можете найти объяснение здесь: stackoverflow.com/questions/7797839/…
camino
75

Имея дело с шаблонными функциями, которые принимают параметры типа шаблона, у вас часто есть универсальные параметры. Универсальные параметры почти всегда являются ссылками того или иного рода. Они также квалифицированы как const-volatile. Таким образом, большинство свойств типов работают с ними не так, как вы ожидали:

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

Решение здесь - использовать 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

Мычание утки
источник
15
Меня это не устраивает. decayочень агрессивен, например, если применяется к ссылке на массив, он дает указатель. ИМХО, как правило, это слишком агрессивно для такого рода метапрограммирования.
dyp 08
7
@SergeRogatch В случае "универсальных параметров" / универсальных ссылок / ссылок пересылки я бы просто remove_const_t< remove_reference_t<T> >, возможно, обернул пользовательскую метафункцию.
dyp 01
1
где вообще используется параметр? Это аргумент func, но я нигде не вижу его использования
Саврам
2
@savram: В этих фрагментах кода: это не так. Мы проверяем только тип, а не значение. Все должно работать нормально, если не лучше, если мы удалим имя параметра.
Mooing Duck
1
@GabrielStaples да. Согласно шаблону, int, const int, int&, const int&, int&&, const int&&, volatile int, volatile int&, volatile int&&, и т.д., все разные.
Mooing Duck,