5

Consider the following code, which has an unreachable call to undefinedFunction.

void undefinedFunction();

template <bool b = false>
void foo()
{
  static_assert(b == false);
  if (b)
    undefinedFunction();
}

int main()
{
  foo();
}

GCC compiles and links this without complaint. With the static_assert, it's hard to see how the compiler could do anything different, but does the standard have anything to say about this? What if the static_assert is removed? Is the compiler at all obligated to remove the branch or can it actually emit an unreachable call instruction that will cause the linker to complain?

Always Confused
  • 470
  • 4
  • 7
  • Determining unreachable code in generic case is not a trivial task... – Jarod42 Apr 08 '16 at 22:07
  • 2
    Removing dead code is the job of the optimizer, not the code generator. It you did not turn it on then, you betcha, you'll get the linker to complain. The standard does not prescribe how an optimizer should do its job. Btw, hard to guess why you can't see this yourself, tinker with the -O option. – Hans Passant Apr 08 '16 at 22:08
  • I believe this is a bug. This code shouldn't compile since when the template function `foo` is instantiated `undefinedFunction` is odr-used. Doesn't matter if branch won't be evaluated cause of `b == false`. – 101010 Apr 08 '16 at 22:14
  • @Hans, I did try with and without optimizations and gcc succesfully builds in both cases, but your point makes sense. – Always Confused Apr 08 '16 at 22:19
  • @HansPassant Optimizer doesn't play any role here. – 101010 Apr 08 '16 at 22:20
  • @101010: It should *compile*, the function is declared. It should not link as there is no definition. – Jarod42 Apr 08 '16 at 22:20
  • @Jarod42 sorry, I meant not link. I'm one of those that consider compile + linking == compile :) – 101010 Apr 08 '16 at 22:20
  • 1
    It may not link - but the program is ill formed, no diagnostic required, so it may also appear to work just fine. – Alan Stokes Apr 08 '16 at 22:22

3 Answers3

7

Normally, calling a function odr-uses it, with two exceptions: if it is pure virtual, or if it is not potentially evaluated ([basic.def.odr]/5). An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof ([basic.def.odr]/2). Unevaluated operands occur in typeid, sizeof, noexcept, and decltype, none of which applies here. Therefore undefinedFunction is odr-used as long as foo is instantiated, which it is. There is no exception for "statically unreachable" code.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Nice summary that possible symbols must be defined. If I'm not mistaken, it's impossible to determine if a code is unreachable in every scenario, so it's ~unguarantible~ – Kahler Apr 08 '16 at 22:38
  • Thank you, excellent explanation - wish I could accept two answers. – Always Confused Apr 08 '16 at 22:38
  • "Normally, calling a function odr-uses it, with two exceptions: if it is pure virtual".. this is wrong. Even if it is pure, it needs to be defined if called. That is only logical: how could the compiler know what the function does, otherwise? – Johannes Schaub - litb Apr 09 '16 at 10:34
  • @JohannesSchaub-litb If all calls dispatch to the final overrider then the pure virtual function doesn't need a definition. I am sure you know this as well as I do. I tried to keep the answer brief, so I didn't mention the exception for when the pure virtual function is called with explicit qualification. – Brian Bi Apr 09 '16 at 18:29
  • @Brian OK, so the meaning of "calling a function foo" in your answer is "naming a function foo in a function call". I was not aware of this, I apologize. For non-pure virtual functions though, it is not the fact that you mentioned it in the expression that makes it need a definition though: They need a definition nontheless. The naming will not affect this in any way. In this way, your wording confused me because the "pure" there seems to say that the "pure" affects the treatment within a function call. But it instead affects the a-priori need of a definition at large. – Johannes Schaub - litb Apr 09 '16 at 19:41
  • BTW, relevant here regarding the "as long as foo is instantiated: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1209 . You may want to add this link to the answer. Both because it may be interesting and so that I can undo my downvote. Cheers – Johannes Schaub - litb Apr 09 '16 at 19:45
4

According to the C++ standard §3.2/p4 One-definition rule [basic.def.odr] (emphasis mine):

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

The template function foo is instantiated undefinedFunction is odr-used (i.e., a definition of undefinedFunction is required). It doesn't matter if if clause is not evaluated. Consequently, the program is ill formed and since no diagnostic is required it may link or it may not.

101010
  • 41,839
  • 11
  • 94
  • 168
1

As others commented, two separate steps contribute to this. The unreachable code may be erased by the optimizer, so the link for undefinedFunction() is never requested. The compilation step does not care for symbols that are not defined (more information about compilation in this community answer).

This is independent of the static_assert. You can have undefined references in template code that never gets initialized, and the compilation succeeds, as the compiler never considers the code, it never emit requirement for the link.

If the symbol gets through, and is requested in some later step, linkage will fail. This is the same that happens when you compile a library with template classes, and later tries to use a template with argument types for which the class was not explicitly initialized, you will get undefined references to types using a library that compiled fine on it's own.

If you wish to exam if you compiler is actually eliminating dead code, this answer details about profiling dead code in GCC.

Community
  • 1
  • 1
Kahler
  • 1,130
  • 6
  • 24