For Better or for Worse, the Overload
You know what’s stuck on my mind? Ever since writing my last post, it’s been the word “better.” It came up when we were talking about overload resolution and implicit conversion sequences. I explained a necessary special case of it—something about how adding const
in a referencebinding is preferred against—and then strategically shut up about the rest.
void run(int (**f)()); // #1
void run(int (*const *f)() noexcept); // #2
int foo() noexcept;
int (*p)() noexcept = &foo;
run(&p); // ???
But it’s so tantalizing, isn’t it? Which one will it choose? How can we reason about this? I can see it in your eyes, sore but eager. You yearn for conversion. Well, I wasn’t going to— I— well… Alright, since you’re so insistent. Just for you. Shall we?
∗ ∗ ∗
Let’s start small and work our way up. An implicit conversion sequence is a standard conversion sequence, possibly followed by a userdefined conversion and another standard conversion sequence in the case of a class type.^{1} A userdefined conversion is something like T::operator S()
, which defines how to convert a T
into an S
. These are easy: they work exactly how we tell them to. So, it evidently suffices to understand standard conversion sequences.
 Definition 1

A standard conversion sequence is a sequence of zero or one conversions from each of the following categories, in order:
 Lvaluetorvalue, arraytopointer, or functiontopointer conversions:
 Lvaluetorvalue: converts a glvalue of nonfunction, nonarray type to a prvalue. Not particularly relevant to overload resolution, and kind of sophisticated, so we’ll mostly forget about this.
 Arraytopointer: converts an expression of type “array of $N$
T
” or “array of unknown bound ofT
” to a prvalue of type “pointer toT
,” applying temporary materialization conversion if the expression was a prvalue (note that GCC has a bug and won’t do this; temporary materialization is defined later).  Functiontopointer: converts an lvalue function of type
T
to a prvalue of type “pointer toT
.”
 Integral/floatingpoint/boolean/pointer/pointertomember conversions and promotions:
 There are a bunch of rules for converting between various integral and floatingpoint types that are necessary but, frankly, menial and uninteresting, so we’ll omit these too. The pointer/pointertomember conversions are probably things you already know.
 Function pointer conversion: converts a prvalue of type “pointer to
noexcept
function” to a prvalue of type “pointer to function.”  Qualification conversion: unifies
const
ness of two types somehow. Oh boy. It can’t be that bad, right? Right?
 Lvaluetorvalue, arraytopointer, or functiontopointer conversions:
Surprise! This post is actually about qualification conversions
OK— OK. Uh. Hear me out.
In C++, const
and volatile
are often called cvqualifiers, so called because they qualify types to form cvqualified types. The cvqualified versions of a cvunqualified type T
are const T
, volatile T
, and const volatile T
. We could also consider types T
which have cvqualifiers nested inside—for example, const int** const
(“const
pointer to pointer to const int
”) could be written alternatively as X
in the following series of type aliases:
using U = const int;
using V = U*;
using W = V*;
using X = const W;
Now, a mathematically inclined reader may choose to write “const
pointer to pointer to const int
” as
where $cv_0=\{\mathtt{const}\}$, $cv_1=\emptyset$, $cv_2=\{\mathtt{const}\}$, $P_0=P_1=\text{``pointer to''}$, and $\mathtt{U}=\mathtt{int}$. More generally, we could write any type $\mathtt{T}$ (not necessarily uniquely) as
$cv_0~P_0~cv_1~P_1~\ldots~cv_{n1}~P_{n1}~cv_n~\mathtt{U}$for some $n\ge 0$ and some type $\mathtt{U}$; each $P_i$ is either “pointer to,” “array of $N_i$,” or “array of unknown size of.” For simplicity, let’s assume each $P_i$ will always be “pointer to.”
Notice that, for determining whether one type can be qualificationconverted into another type (e.g., trying to convert int*
to const int*
), we can always drop $cv_0$ from consideration altogether—in particular, at the top level, we can always initialize a const T
from a T
and vice versa, and likewise we can always convert from one to the other. So, let’s forget about $cv_0$.
Since we don’t care as much about any of the $P_i$ or $\mathtt{U}$—these are the “nonconst
y” parts, and we’ll deal with them separately—let’s write this even more compactly as the $n$tuple $(cv_1,cv_2,\ldots,cv_n)$. The longest possible such tuple is called cvqualification signature of $\mathtt{T}$.
We’re almost there. I’m trying really hard to make the C++ standard more palatable here, so bear with me. Two types $\mathtt{T1}$ and $\mathtt{T2}$ are called similar if they have cvdecompositions of equal size such that each of their respective $P_i$’s are either (1) the same, or (2) one is “array of $N_i$” and the other is “array of unknown size of”; and, moreover, their $\mathtt{U}$’s should agree. Basically, if the “notconst
y” parts of their cvdecompositions mostly agree, they’re called “similar.”
OK. It’s time. I’m only barely paraphrasing the standard because it’s all I can do at this point—it’s honestly worded pretty tightly. Let $\mathtt{T1}$ and $\mathtt{T2}$ be two types. Then, their cvcombined type $\mathtt{T3}$, if it exists, is a type similar to $\mathtt{T1}$ such that, for each $i>0$:
 $cv_i^3=cv_i^1\cup cv_i^2$