I have previously written about code rot (code decay). This post is about decay in a different context. Essentially, there are three sets of types in C++ that will decay, lose information. This entry will describe the concept, the circumstances, and in some cases ways to avoid type decay from occurring. This is an important topic for me to cover because the addition of support for arrays in Alchemy would have been much more difficult without knowledge of this concept.
Why do certain types decay? Maybe because they have a short half-life?! I actually do not know the reasoning behind all of the rules. I suspect they exist mostly to help things run much smoother. Type decay is a form of syntactic sugar. This is because the original type,
T1, is attempting to be used in a context that does not accept that type. However, it does accept a type
T1 can be converted to.
Generally, the circumstances involve attempting to use a type
T1, in an expression, as an operand, or initializing an object that expects a type
T2. There are other special cases such as a
switch statement where
T2 is an integral type or when the expression
T2 reduces to a bool.
The rules are quite involved. For details on the rules for order of conversion, I recommend the page on Implicit Conversions[^] at cppreference.com.
I am only going to delve into the implicit cast scenarios that relate to
Lvalue transformations. This may sound redundant, but an
Lvalue transformation is applied when an
lvalue argument is used in a context where an
rvalue is expected. Well, it's a lot more redundant if you substitute
lvalue is a type that can appear on the left hand of an assignment expression. In order for a type to qualify as an
lvalue, it must be a non-temporary object or a non-temporary member function. This basically says that a data type with storage will exist when the time comes to write to storage.
rvalue is just the opposite. It is an expression that identifies a temporary object and is not a value associated with any object. Literal values and function call return values are examples of
rvalues, as long as the return type is not a reference.
L-value to R-value
This type of conversion occurs in order to allow expressions to be assigned in a series of expressions, or as a result of situations where
rvalues are not present. Such as a function that returns a reference to a type.
For this implicit conversion scenario, the
lvalue is effectively copy-constructed into a temporary object so that it qualifies as an
rvalue type. Other potential conversion adjustments may be made as well such as removing the cv-qualifiers (
This is a fairly benign scenario of type decay, unless your
lvalue type has an extremely expensive copy-constructor.
Function to Pointer
The second scenario is another simple case. If the
lvalue is a function-type, not the actual expression of a function call, just the type, it can be implicitly converted to a pointer. This explains why you can assign a function to an expression that requires a function pointer, yet you are not required to use the
& to take the address of the function. Although if you do, you will still get the same results, because the implicit conversion no longer applies to the pointer to a function.
Array to Pointer Conversion
This is the case that I needed to understand in order to successfully add support for arrays to Alchemy. If an
lvalue is an array-type
T with a rank of
lvalue can be implicitly converted to a pointer to
T. This pointer refers to the first element in the original array.
I have been using C++ for almost two decades, and I am surprised that I did not discover this before now. Take a look at the following code. What will it print when compiled and run on a 64-bit system?
void decaying(char value)
std::cout << "value contains " << sizeof (value) << "bytes\n";
Hopefully, you surmised that since
T1 is open to the implicit conversion to a pointer to a
sizeof call will return the size of a 64-bit pointer. Therefore, this
string would be printed "
T1 contains 8 bytes".
I discovered this when I was building my Alchemy unit-tests to verify that the size of an array data type was properly calculated from a
TypeList definition. It only took a little bit of research for me to discover there is actually a special declaratory that can be used to force the compiler to prevent the implicit conversion of the array. Depending on your compiler and settings, you may get a helpful warning when this conversion is applied.
This declarator is called a
noptr-declarator. To invoke this declaratory, use a
&& in front of the name of the array. Parenthesis will also need to be placed around the operators and the name of the array. The resulting definition becomes a pointer or a reference to an array of type
T, rather than simply a pointer to
T. The sample below shows the declaration that is required to avoid the implicit cast.
void preserving(char (&value))
std::cout << "value contains " << sizeof (value) << "bytes\n";
Here is a brief example to demonstrate the syntax and differences:
int main(int argc, char* argv)
std::cout << "input contains " << sizeof(input) << " bytes\n";
main: input contains 24 bytes
decaying: value contains 8 bytes
preserving: value contains 24 bytes
This simple modification allowed me to preserve the type information that I needed to properly process array data types in Alchemy. In my next entry, I will demonstrate how template specialization can be used to dismantle the array to determine the type and the rank (number of elements) that are part of its definition.
This function is part of the C++ Standard Library starting with C++ 14. It can be used to programmatically perform the implicit casts of type decay on a type. This function will also remove any cv-qualifiers (
volatile). Basically, the original type
T will be stripped down to its basic type.
I haven't had a need to use this function in Alchemy. However, it is helpful to know about these utility functions and what is possible if I ever find the need to extract only the type.
The C++ compiler is a very powerful tool. Sometimes, it attempts to coerce types and data into similar forms in order to compile a program. In most cases, this is a very welcome feature because it allows for much simpler expressions and reduces clutter. However, there are some cases where the implicit casts can cause grief.
I stumbled upon the array to pointer type decay conversion during my development of Alchemy. Fortunately, there are ways for me to avoid this automatic conversion from occurring and I was able to work through this issue. Subtleties like this rarely appear during development. It is definitely nice to be aware that these behaviors exist, so you can determine how to work around them if you ever encounter one.
I am a software architect and I have been developing software for nearly two decades. Over the years I have learned to value maintainable solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development. I use the most beneficial short-term achievements to drive the software I develop towards a long-term vision.
• Consumer Products
• Computer Infrastructure Management
• DoD Contracting
My experience spans these hardware types and operating systems:
o Windows (Full-stack: GUI, Application, Service, Kernel Driver)
o Linux (Application, Daemon)
• Mobile Devices
o Windows CE / Windows Phone
• Embedded Devices
o VxWorks (RTOS)
o Greenhills Linux
o Embedded Windows XP
I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.
I am the creator of an open source project on GitHub called Alchemy
], which is an open-source compile-time data serialization library.
I maintain my own repository and blog at CodeOfTheDamned.com/
], because code maintenance does not have to be a living hell.