Can the semantics of a well-formed C++ translation unit depend on the
presence of a function or function template declaration that is never
used? By “used”, I mean it is selected by overload resolution, is
implicitly or explicitly specialized (if a template), or is a definition
and has external linkage.
For example, suppose I have something like:
static int f(int) { return 1; }
static int f(float) { return 2; }
static int f(char) { return 3; }
int main()
{
return f(9); // returns 1
}
and then I delete one or both of the non-selected overloads:
static int f(int) { return 1; }
int main()
{
return f(9); // returns 1
}
can the resulting translation unit have different semantics than the
original? “Different semantics” could mean that it is now ill-formed
(whether or not a diagnostic is required), or that it is well-formed but
the set of potential run-time behaviors is different.
Intuitively, it seems like the answer should be no, and a
straightforward reading of the relevant standard passages (focusing on
selection by overload resolution as the usage method) seems to
confirm this. Specifically, C++17 16.3 pp2-3 says:
(p2) Overload resolution selects the function to call in seven distinct
contexts within the language:[…]
Each of these contexts defines the set of candidate functions and the
list of arguments in its own unique way. But, once the candidate
functions and argument lists have been identified, the selection of
the best function is the same in all cases:
(2.8) First, a subset of the candidate functions (those that have
the proper number of arguments and meet certain other conditions) is
selected to form a set of viable functions (16.3.2).(2.9) Then the best viable function is selected based on the
implicit conversion sequences (16.3.3.1) needed to match each
argument to the corresponding parameter of each viable function.(p3) If a best viable function exists and is unique, overload resolution
succeeds and produces it as the result. Otherwise overload resolution
fails and the invocation is ill-formed. When overload resolution
succeeds, and the best viable function is not accessible (Clause 14)
in the context in which it is used, the program is ill-formed.
Then, 16.3.3 p2 says:
If there is exactly one viable function that is a better function than
all other viable functions, then it is the one selected by overload
resolution; otherwise the call is ill-formed.
This procedure seems to have the property that the best viable
function F from a set S, if it exists, will be the same as for any
subset of S that contains F, because F must have been better than every
other element in S, so it would also be better than all other functions
in any subset of S.
And if that is true, then I think that means that deleting a
declaration that is never selected by overload resolution cannot alter
the semantics of a well-formed translation unit, since it could only
cause each overload resolution that is performed to happen with a subset
of the original set of candidates, and hence yield the same result.
But, perhaps there is a way that the presence of a declaration, which
ultimately does not get selected, nevertheless affects the set of
candidates, maybe by way of
argument-dependent lookup?
The full lookup rules are quite complex. Or, what if an unused declaration
has a default argument expression that triggers a template instantiation
that manages to affect the semantics elsewhere (without undefined behavior)?
This sort of question, asking to prove a negative, is inherently difficult
to answer. The ideal answer would be either a counterexample or a reference
to somewhere in the standard or the committee’s rationale (do they even do
that anymore?) clarifying that
the language is intended to have the property that deleting an unused
declaration cannot affect semantics. Alternatively, I’d likely
accept a reasonably well-researched answer exploring other angles (like the
candidate inhibition and default argument ideas) and arriving at a negative
conclusion, similar to what I have above.
Note: The essence of this question relates to functions that are “unused”
according to a thorough but fundamentally syntactic examination of the AST
after parsing and semantic analysis is finished. I have
chosen to formalize “used” as “selected by overload resolution” (or is
specialized, or is a definition with external linkage) since I
think that captures almost every case of usage, but there are a few exceptions;
for example, there are implementation-defined things
like the GCC
constructor
attribute that lead to a call, and again should be treated as a use.
What I’m after, in an answer, is information about cases where there is no apparent use of a
function, even when taking into account perhaps obscure but nevertheless straightforward
mechanisms (especially when they cause the function to be called), but the
semantics is affected by its presence.
I tried several searches but didn’t find anything relevant, in part
because there are so many hits for things only tangentially related.
(The motivation for this question comes from a project to build a
dependency analysis that would, among other things, automatically delete
unused declarations. I’d like to
be sure that I can do so without altering the meaning of the program.)