Spooky Halloween C++ Special!
A Compendium of Cryptic Characters
C++ is blessed with a plethora of gotchas, traps, caveats, pitfalls and footguns. Within the C++ dungeons lurk many shady characters. ‘Tis the time of year to meet some of these bountifully spawned beasts.
🎃
⛄ Abominable Types
“There is a dark corner of the type system that is little known other than to compier writers…”
– Alisdair Meredith, Abominable Function Types
An abominable function type is the type produced by writing a function type followed by a cv-ref qualifier.
using abominable = void() const volatile &&;
abominable
names a function type, not a reference type, and despite appearances, is neither a const
nor a volatile
qualified type. There is no such thing as a cv-qualified function type in the type system, and the abominable function type is something else entirely.
Note that it is impossible to create a function that has an abominable type!
“The only examples I have of explicitly writing these types fall into the category of showing off knowledge of the corners of compilers, and winning obfuscated coding contests.
I have not yet encountered the following idiom in real-world usage outside of such scenarios.” – ibid
struct rectangle
{
using int_property = int() const; // common signature for several methods
int_property top, left, bottom, right, width, height; // declare property methods!
// ... ^^^^^^^
};
Spooked? Curious? Read more in Tales of the Abominable Function Types!
Mwahahahah…
🎃
👽 Aliens
Bishop: No, the hard-wiring between here and there was damaged. We can’t align the dish.
Ripley: Well, then somebody has got to go out there, take a portable terminal, and patch in manually.
– Aliens 1986
Stretching it a bit thin so forgive my “silly” typo, and let’s mention alignas
(which when squinting hard enough you might just read as aliens
) and its kin. The alignas
keyword specifier was introduced in C++11. It specifies the alignment requirement of a type or an object.
Every object type has the property called alignment requirement, which is an integer value (of type std::size_t
and always a power of 2) representing the number of bytes between successive addresses at which objects of this type can be allocated. The alignment requirement of a type can be queried with alignof
or std::alignment_of
. The pointer alignment function std::align
can be used to obtain a suitably-aligned pointer within some buffer, and std::aligned_storage
can be used to obtain suitably-aligned storage. Each object type imposes its alignment requirement on every object of that type; stricter alignment (with larger alignment requirement) can be requested using alignas
. In order to satisfy alignment requirements of all non-static members of a class, padding may be inserted after some of its members.
🎃
👹 Demons
“Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to having demons fly out of your nose.”
– John F. Woods, comp.std.c 1992
The most notorious and infamous of all is probably the Nasal Demon of Undefined Behavior.
With its ancient origins in the C language, it predates many of the other members of this list. Nevertheless, it is still an ever present threat and terror to the unsuspecting journeyman.
In short, Undefined Behavior (UB) renders the entire program meaningless if certain rules of the language are violated.
A lot has been written about UB. John Regehr has a facinating series on UB and an update on Undefined Behavior in 2017 too. His two part CppCon 2017 “Undefined Behavior in 2017” videos are online too.
STARTLING NEW EPIC: In Sept. 2017, the Demon showed it still has what it takes when a short UB snippet went viral:
#include <cstdlib> // for system()
typedef int (*Function)(); // typedef function pointer type
static Function Do; // define function pointer, default initialized to 0
static int EraseAll() { return system("rm -rf /"); } // naughty function
void NeverCalled() { Do = EraseAll; } // this function is never called!
int main() { return Do(); } // call default-initialized function=UB: chaos ensues.
which Clang compiles to
main:
movl $.L.str, %edi
jmp system
.L.str:
.asciz "rm -rf /"
That is, the compiled program executes rm -rf /
even though the original program never calls EraseAll()
! However, Clang is allowed to do this since the function pointer Do
is initialized to 0
as it is a static variable, and calling 0
invokes undefined behavior – but it may seem strange that the compiler chooses to generate this code. It does, however, follow naturally from how compilers analyze programs…
Read more about this cryptic tale here.
🎃
👿 DLL Hell
“There is no greater sorrow then to recall our times of joy in wretchedness.”
― Dante Alighieri, Inferno
DLL Hell is a term for the complications that arise when working with dynamic link libraries (DLLs) used with Microsoft Windows operating systems.
DLL Hell can manifest itself in many different ways in which applications do not launch or work correctly. Like Dante’s Inferno Circles of Hell, DLL Hell is the Windows ecosystem-specific form of the general concept Dependency Hell.
🎃
🦆 Duck Typing
“If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction.”
– The Internets on the Liskov Substitution Principle
Duck typing is an application of The Duck Test in type safety.
The Duck Test is a form of abductive reasoning.
This is its usual expression:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
“Classic” Duck Typing requires that type checking be deferred to runtime and is mostly relevant to dynamically typed languages (unlike C++). However, templates or generic functions or methods apply the duck test in a static typing context.
In fact, one of the main reasons for C++ Concepts (if/when) they finally land, is to bring a more disciplined approach to template type specification and … well..
Be vewy vewy quiet… it’s Duck Typing Season.
Concepts vs. Duck Typing
Wead mowe hewe.
🎃
🛸 Flying Saucers
“Unknown objects are operating under intelligent control…
It is imperative that we learn where UFOs come from and what their purpose is…”
– Admiral Hillenkoetter, First CIA Director 1960
C++20 may see the invasion of a new operator into the language:
The <=> Spaceship Operator!
This is a single, 3-way comparison, <=>
operator, that once defined allows the compiler to automatically generate all other comparison operators: <
, <=
, ==
, !=
, >=
, >
. It provides a consistent interface and support for partial ordering and other goodies.
Walter E. Brown gave a lightning talk about it at CppCon 2017 and the latest proposal is P0515R2.
🎃
😈 Imps
“It’s easy to confuse ‘what is’ with ‘what ought to be,’ especially when ‘what is’ has worked out in your favor.”
– Tyrion Lannister, A.K.A. “The Imp”
The C++ standard mentions undefined behavior Demon’s two less dangerous brothers, unspecified behavior and the implementation-defined behavior Imp.
Implementation-defined behavior: unspecified behavior where each implementation documents how the choice is made.
In implementation-defined behavior the implementation is required to document/guarantee what exactly is going to happen, while in case of unspecified behavior the implementation is not required to document or guarantee anything.
Imps come in many form - here’s an impressive (if disheartening) list of known Imps.
Read more if you dare!
🎃
🕴️ Shadow Variables
“Only a lone foe could pierce that cordon; once inside, he would have to move by stealth, and strike with power and suddenness. I chose that mission.”
-– The Shadow, Shadow Magazine #131 1937
Variable shadowing occurs when a variable declared within a certain scope (e.g a block, a function) has the same name as a variable declared in an outer scope. The outer variable is said to be shadowed by the inner variable, while the inner identifier is said to mask the outer identifier. This can lead to confusion, as it may be unclear which variable subsequent uses of the shadowed variable name refer to, which depends on the name resolution rules of the language. In each scope, the same name or identifier may refer to a different variable with a completely different type.
Variable shadowing is by no means limited to C++.
See an extreme example here.
bool x = true; // x is a bool
auto f(float x = 5.f) { // x is a float
for (int x = 0; x < 1; ++x) { // x is an int
[x = std::string{"Boo!"}](){ // x is a std::string
{ auto [x,_] = std::make_pair(42ul, nullptr);} // x is now unsigned long
}();
}
}
🎃
Terminators
“Hasta la vista, baby!”
– Terminator
C++ provides surprisingly numerous ways for a program to terminate. These include both normal and unexpected termination.
When writing robust software and libraries, it is important to be be aware of the various ways your code might abruptly end. Similarly, many of these conditions might occur at module-boundaries such as DLLs.
Amongst the standard C++ program terminators we find: multiple flavors of std::exit()
, std::abort()
, std::terminate()
, std::signal()
and std::raise()
.
I’ve written about, and mapped, some of these in my Terminators post.
🎃
👻 Transparent Objects
“a thing and not a man; a child, or even std::less<>
– a black amorphous thing.”
― Ralph Ellison, Invisible Man
Introduced in C++14, a transparent function object accepts arguments of arbitrary types and uses perfect forwarding, which avoids unnecessary copying and conversion when the function object is used in heterogeneous context, or with rvalue arguments. In particular, template functions such as std::set::find
and std::set::lower_bound
make use of this member type on their Compare types.
Notable Transparent Function Objects include std::less<>
and std::equal_to<>
.
🎃
🦄 Unicorns
“Good news everyone!
I’ve implemented C++ Unicorn Call syntax”
― JF Bastien, Twitter 2016
The Unified Call Syntax proposal, proposes the idea that f(x,y)
can invoke a member function x.f(y)
, if there are no f(x,y)
. The inverse transformation, from x.f(y)
to f(x,y)
is not proposed.
The motivation for Unified Call Syntax: “Today, we already have the problem that many standard-library types are supported by two functions, such as, begin(x)
and x.begin()
, and swap(x,y)
and x.swap(y)
. This is an increasing problem.
The problem was solved for operators. An expression a+b
may be resolved by a free-standing function operator(X,X)
or a member function X::operator(X)
. The problem was solved for range-for
by allowing both begin(X)
and X::begin()
to be found. The existence of two special-case solutions and a lot of duplicated functions indicate a general need. Each of the two notations has advantages (e.g., open overload sets for non-members and member access for members) but to a user the need to know which syntax is provided by a library is a bother.”
There are still many concerns about Unified Call Syntax and legacy code and it has not yet been accepted into the C++ language.
However, the Unicorn Call Syntax will brighten up the most dreary of code bases:
struct 🦄 {
🦄(int _🦄) : _🦄(_🦄) {}
operator int() { return _🦄; }
int _🦄;
};
🦄 operator ""_🦄(unsigned long long _🦄) { return _🦄; }
int main() {
auto unicorn = 42_🦄;
return unicorn;
}
[ Whoda thunk The Unicorn is a supervillain! ]
🎃
💀 Voldemort Types
“I can make things move without touching them.”
– Lord Voldemort, A.K.A “He-Who-Must-Not-Be-Named”, HP&HBP
A Voldemort Type is a type that cannot be directly named outside of the scope it’s declared in, but code outside the scope can still use this type.
Voldemort Types are inspired by the D language and function similarly in C++. See it in action here. Walter Bright’s has written about them here.
In the example below, Voldemort
is a local type inside createVoldemortType()
, an auto
returning lambda, which returns a Voldemort
instance to the caller. Despite being unable to name Voldemort
inside main()
we can still use variables of that type as we would any regular type.
int main()
{
auto createVoldemortType = [] // use lambda auto return type
{
struct Voldemort // localy defined type
{
int getValue() { return 21; }
};
return Voldemort{}; // return unnameable type
};
auto unnameable = createVoldemortType(); // must use auto!
decltype(unnameable) unnameable2; // but, can be used with decltype
return unnameable.getValue() + // can use unnameable API
unnameable2.getValue(); // returns 42
}
Voldemort types can sometimes be used as a poor man’s OOP annonymous types for factory-like operations. This is stack-based polymorphism with no pointers and no dynamic allocation:
struct IFoo // abstract interface
{
virtual int getValue() = 0;
};
inline auto bar(IFoo& foo) { return foo.getValue(); } // calls virtual interface method
int main()
{
auto fooFactory = []
{
struct VoldeFoo: IFoo // local Voldemort type derived from IFoo
{
int getValue() override { return 42; }
};
return VoldeFoo{};
};
auto foo = fooFactory();
return bar(foo); // works as expected, returns 42.
}
🎃
🧟 Zombies
“We’ve got zombies in the C++ standard.
There are two factions: one of them stating that it is ok to have well defined zombies, while some people think that you’d better kill them.”
– Jens Weller, C++ and Zombies
What happens to an object in scope after it is moved-from?
Without destructive move (which is not currently supported in C++), the lifetime of the remaining object “husk” carries on in a zombie-like state.
“When you implement move constructors and assignment operators, you actually not just have to care about the move, but also about what is left behind. If you don’t, you might create a zombie: an object, whose value (aka life) has moved somewhere else.”
Eric Niebler’s guideline urges one to leave the object in a “minimally conscious state”: “Moved-from objects must be in a valid but unspecified state”. On the other hand, Sean Parent believes that ” you need to either kill the zombies or resurrect them, the standard wants to let them walk around, and if you’ve ever watched “The Walking Dead” then you know how that ends.” (see his full comment in the comments below).
Despite appearances, zombies have little to do with std::decay
.
🎃
🧟♀️ Zombies & Brains 🧠
“Brains: names that want to eat your, [zombie.names]”
– Richard Smith, [Stephan T. Lavavej], The Holy ISO C++ Standard Index
OK, children are you now ready to be truely creeped out?
Open your Holy ISO C++ Standards to 20.5.4.3.1 Zombie names.
These appear in the Holy Standard index as:
“brains, names that want to eat your, [zombie.names]” and
“living dead, name of, [zombie.names]”
(I kid you not - whoever put that there was waiting for this post!).
We have entered the Holy Standard’s crypt, where previously standardized and later deprecated std
names are laid to rest. Esteemed members include auto_ptr
, binary_function
, bind1st
, bind2nd
, random_shuffle
, unary_function
, unexpected
and unexpected_handler
.
However, you never know when one of these decrepid denizens will suddenly appear in legacy code.
🎃
Terminus
C++ provides a rich seam of inspiration for all your creepy Halloween needs!
Nevertheless, I am quite sure this list is incomplete. If I missed any other denizens of the C++ depths, please do let me know in the comments below, on Twitter, Reddit or find me on the C++ Slack channel.
I may add them here or cage them until next year…. Mwahahaha!!
(As the 2017 Zombie 🧟🧟♀️🧠 and Flying Saucer 🛸 Emojis are adopted on more platforms they will gradually make their appearance here. Here’s a ⚰️ and 🚀 for you to enjoy until then.)
🎃
I ❤ feedback: If you dug this post, found it fun or terrifying or found an error, do drop me a line.