What are the reasons for the existence of std::decay
?
In what situations is std::decay
useful?

- 3,671
- 3
- 28
- 38
-
7It is used in the standard library e.g. when passing arguments to a thread. Those need to be *stored*, by value, so you cannot store e.g. arrays. Instead, a pointer is stored and so on. It is also a metafunction that mimics the function parameter type adjustments. – dyp Sep 08 '14 at 20:25
-
5`decay_t
` is a nice combination, to see what `auto` would deduce. – Marc Glisse Sep 08 '14 at 20:37 -
[meta.trans.other] states: This behavior is similar to the [... = several] conversions applied when an lvalue expression is used as an rvalue, but also strips cv-qualifiers from class types **in order to more closely model by-value argument passing**." – dyp Sep 08 '14 at 20:43
-
14std::decay() can do three things. 1 It is able to convert an array of T to T*; 2. It can remove cv qualifier and reference; 3. It converts function T to T*. e.g decay(void(char)) -> void(*)(char). Seems no one mentioned the third usage in the answers. – r0n9 Oct 12 '17 at 23:48
-
@r0ng It actually does *none* of these things, since it isn’t a function. – Konrad Rudolph Apr 12 '20 at 17:11
2 Answers
<joke>It's obviously used to decay radioactive std::atomic
types into non-radioactive ones.</joke>
N2609 is the paper that proposed std::decay
. The paper explains:
Simply put,
decay<T>::type
is the identity type-transformation except if T is an array type or a reference to a function type. In those cases thedecay<T>::type
yields a pointer or a pointer to a function, respectively.
The motivating example is 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);
}
which accepted its parameters by value to make string literals work:
std::pair<std::string, int> p = make_pair("foo", 0);
If it accepted its parameters by reference, then T1
will be deduced as an array type, and then constructing a pair<T1, T2>
will be ill-formed.
But obviously this leads to significant inefficiencies. Hence the need for decay
, to apply the set of transformations that occurs when pass-by-value occurs, allowing you to get the efficiency of taking the parameters by reference, but still get the type transformations needed for your code to work with string literals, array types, function types and the like:
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));
}
Note: this is not the actual C++11 make_pair
implementation - the C++11 make_pair
also unwraps std::reference_wrapper
s.

- 133,968
- 17
- 288
- 421
-
2"T1 will be deduced as an array type, and then constructing a pair
will be ill-formed." what is the problem here? – camino May 25 '16 at 20:36 -
7I get it, in this way we will get pair
which can only accept strings with 4 characters – camino May 26 '16 at 15:46 -
@camino I don't get it, are you saying that without std::decay the the first part of the pair would occupy 4 bytes for four chars instead of a one pointer to char? Is that what the std::forward does? Stops it from decaying from an array to a pointer? – Zebrafish Oct 04 '17 at 03:30
-
5@Zebrafish It is array decay. For example: template
void f(T&); f("abc"); T is char(&)[4], but template – camino Oct 06 '17 at 00:14void f(T); f("abc"); T is char*; You can also find an explanation here: https://stackoverflow.com/questions/7797839/why-does-the-array-decay-to-a-pointer-in-a-template-function -
Late to the party, but can you confirm that the pair
instantiation will still "work" as it is convertible into pair – Adrian Muljadi Mar 03 '21 at 12:02, but the issue here is that this will generate too many function template instantiations? one for pair , pair , .... -
-
@camino Quoting the paper: "the above code would fail to compile because we suddenly create a temporary pair object of the type `std::pair
` (**and this instantiation fails because arrays are not constructible with the T() syntax, nor are they copy-constructible**)" – Alexey Romanov Mar 19 '21 at 11:30 -
1@camino So maybe guaranteed copy elision solves the problem as well, at least in this case, but of course it wasn't available yet. – Alexey Romanov Mar 19 '21 at 11:39
When dealing with template functions that take parameters of a template type, you often have universal parameters. Universal parameters are almost always references of one sort or another. They're also const-volatile qualified. As such, most type traits don't work on them as you'd expect:
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
The solution here is to use 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";
}

- 64,318
- 19
- 100
- 158
-
17I'm not happy with this. `decay` is very aggressive, e.g. if applied to a reference to array it yields a pointer. It is typically too aggressive for this kind of metaprogramming IMHO. – dyp Sep 08 '14 at 20:28
-
-
9@SergeRogatch In the case of "universal parameters" / universal references / forwarding references, I'd just `remove_const_t< remove_reference_t
>`, possibly wrapped in a custom metafunction. – dyp Aug 01 '17 at 11:11 -
1where is param even being used? It's an argument of func but I don't see it being used anywhere – savram Sep 17 '17 at 00:39
-
2@savram: In these pieces of code: it isn't. We're only checking the type, not the value. Everything should work fine if not better if we removed the name of the parameter. – Mooing Duck Sep 18 '17 at 17:22
-
@MooingDuck *Universal parameters are almost always references of one sort or another.* --> Is there any case in which universal reference aren't references? – JFMR Jul 13 '19 at 10:34
-
@ElProfesor: Interesting question. I suppose there's some cases where it might deduce as `func
`, maybe when given the return value of a function that returns an `int`? Specifying it explicitly will make it definitely not a reference. – Mooing Duck Jul 14 '19 at 04:57 -
@MooingDuck a call to a function that returns `int` (i.e., a function that returns an `int` *by value*) is a *prvalue*. `T` will be deduced to `int` and `param` will result in `int&&` (it does make sense so that it can be moved). So, `param` is still a reference for that case. If you can think of any case where `param` won't be a reference, let me know please. – JFMR Jul 14 '19 at 08:49
-
@ElProfesor: I think all that leaves is when the caller specifies it explicitly. `func
(3)` – Mooing Duck Jul 16 '19 at 17:50 -
@MooingDuck I think calling `func
(3)` will instantiate the template function `func` as `void func(int&& param)`. So, `param` would still be a reference. Am I missing something? – JFMR Jul 16 '19 at 18:57 -
@ElProfesor: `param` is a universal reference, not an rreference, so reference folding kicks in. I believe `func
(3)` instantiates `void func(int param)`, but [my tests have sketchy conclusions](http://coliru.stacked-crooked.com/a/45fd4abb8c3cd35b) – Mooing Duck Jul 16 '19 at 22:36 -
@MooingDuck You are looking at `T`, but `param`'s type is the one resulting from `T&&` after reference collapsing is applied. According to [this](http://coliru.stacked-crooked.com/a/f4c7f2c32a54fad0), `func
(3)` seems to be `void func(int&& param)` (look at the type of `f`). – JFMR Jul 17 '19 at 06:42 -
Why does the first one not work? Because `param` is an r-value `int&&`? And an r-value int apparently isn't an int, right? – Gabriel Staples Oct 23 '20 at 00:31
-
4@GabrielStaples yes. According to template, `int`, `const int`, `int&`, `const int&`, `int&&`, `const int&&`, `volatile int`, `volatile int&`, `volatile int&&`, etc are all different. – Mooing Duck Oct 23 '20 at 16:09