9

In a bit of serialization code for a project I'm working on I have a type whose size is compiler dependent. In order to deal with this, I decided to use a template specialization, which works great. Everything is resolved at compile time. The code looks a little bit like this (not the real code, just an example):

template <int size>
void
special_function()
{
     std::cout << "Called without specialization: " << size << std::endl;
}

template <>
void
special_function<4>()
{
     std::cout << "dword" << std::endl;
}

template <>
void
special_function<8>()
{
     std::cout << "qword" << std::endl;
}

int
main()
{
     special_function<sizeof(int)>();
     return 0;
}

On my 32-bit system, executing the above program outputs dword, as expected. But the whole point of doing it this way and not just doing if (sizeof(int) == 4) { ... } else if ... is that I had hoped that the compiler would only produce code for the appropriate function. Since special_function<4> is the only one called in this program, I expected it to be the only one generated by the compiler (gcc 4.1.2 in this case, on x86 Linux).

But that is not the observed behavior.

While it indeed works, the code for each template specialization is generated despite not being ever used. The generic definition is not generated, however.

I should mention that this is a one-step compilation, not a compilation into intermediary object files followed by a link. In that case it would seem natural to defer dead code removal to the link stage, and I know that linkers are not always terribly good at this.

Does anyone know what is going on? Is there a subtlety of template specialization I'm missing here? Lord knows the devil is in the details with C++.

EDIT: Since it's been mentioned, this behavior occurs with both -O3 and -Os.

EDIT2: Rob below suggested putting the functions in an anonymous namespace. Doing so and compiling with any level of optimization does indeed remove the dead code, which is good. But I was curious, so I tried doing the same with the following program:

namespace {
void foo() { std::cout << "Foo!" << std::endl; }
void bar() { std::cout << "Bar!" << std::endl; }
}

int
main()
{
       foo();
       return 0;
}

The idea here is see whether or not Rob's solution is actually related to template specializations. As it turns out, the above code compiled with optimizations turned on elides the unused definition of bar() from the executable. So it seems that while his answer solves my immediate problem, it doesn't explain why template specializations that aren't used are compiled at all.

Does anyone know of a relevant snippet from the standard that would explain this? I always thought templates were generated only on use, but perhaps this is not so for full specializations ...

808140
  • 205
  • 2
  • 11
  • Enable optimizations. Compiler will not include unused code. This is called "Dead code elimination". –  Jan 30 '13 at 16:38
  • 1
    As long as the compiler can figure out that it is never called, yes, optimisation will remove those functions. – Mats Petersson Jan 30 '13 at 16:43
  • 4
    Full specializations are no longer templates and behave more like ordinary functions. They have to be `inline` when used in headers, for example. – Bo Persson Jan 30 '13 at 16:46
  • Vlad, Mats: I should have specified, I attempted this at both -O3 and with -Os. No difference whatsoever. I will edit to clarify. – 808140 Jan 30 '13 at 16:50
  • @808140: How are you verifying that the unused specializations have not been eliminated? – NPE Jan 30 '13 at 16:54
  • NPE: By looking at the assembler output and by running strings on the executable ("qword" shows up as one of them, it ought to have been elided.) – 808140 Jan 30 '13 at 16:59
  • I've tested your code with ordinary functions in place of `special_function<4>` and `special_function<8>`. They aren't getting either. – NPE Jan 30 '13 at 17:05
  • @BoPersson do you have a standard citation supporting this explanation? If you did I would accept that as an answer over Rob's. – 808140 Jan 31 '13 at 10:44
  • @808140 - No, I don't think it is spelled out explicitly, but follows from the rules in *14.8 Function template specializations*. Lots of rules, and spread out over the entire section (20 pages!). – Bo Persson Jan 31 '13 at 12:26
  • I just had the same problem and have searched for at least two hours. I suggest you make this an answer and accept it because it seems that the base template specialization gets compiled into code even for a compilation unit that does nothing more than include a header with such a specialization. Then in thel inker stage you get `multiple definition of ...' errors :-( – hochl Aug 30 '13 at 12:09

2 Answers2

10

The template specializations in your example are functions with external linkage. The compiler cannot know that they won't be called from another translation unit.

On my g++ 4.7.2 Ubuntu system, placing the templates into an anonymous namespace and compiling with -O3 prevented the unused function from being generated.

Similarly, declaring the function template static had the desired effect.

Cyclic3
  • 149
  • 2
  • 10
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Rob: I just repeated your test, and that is indeed the case! In fact, on my system, putting the functions in an anonymous namespace elided them even at `-O` level optimization. However, this appears to be how it works with unused "normal" (i.e. non-template specialization) functions as well. – 808140 Jan 30 '13 at 17:06
  • "The compiler cannot know that they won't be called from another translation unit." True, but shouldn't the linker be able to figure that out? – NPE Jan 30 '13 at 17:06
  • 1
    @NPE: I think that if you use -fwhole-program in the compile stage, or if you use -ffunction-sections -fdata-sections and -Wl,--gc-sections then the linker will remove unused functions. – Zan Lynx Jan 30 '13 at 17:09
  • @ZanLynx: If that works (I can't test it right now), it might be worth writing it up as an answer. – NPE Jan 30 '13 at 17:11
  • 1
    While this doesn't actually answer my question, it did provide a work-around, and so I am accepting. It seems that my understanding of C++ template specializations was incomplete (no surprise there). – 808140 Jan 31 '13 at 10:43
  • g++ seems to incorrectly compile unused templates. The answer here mention "The compiler cannot know that they won't be called from another translation unit." as a defense of g++. This is an invalid defense since for template functions the definitions must be in all translation units that use it. Visual Studio 2015 does this correctly ie it does not try to compile unused templates. – John Paul Dec 27 '16 at 21:35
  • GCC ignores all optimisation levels greater than 3, treating them as O3. I have edited this answer accordingly. – Cyclic3 Dec 27 '18 at 08:25
3

This is a peculiar problem. I looked into it a little, and this issue is unrelated to template specialization. I guess g++ doesn't, by default, strip unused symbols. This makes sense in case you later want to link your output to another program.

However, there are command line options that you can use to strip unused symbols. For details, see this post:

How to remove unused C/C++ symbols with GCC and ld?

but also see here

Using GCC to find unreachable functions ("dead code")

and here

Dead code detection in legacy C/C++ project

Just to try this out, I modified the code as follows:

#include <iostream>

void junk_function() {
    std::cout<<"test" << std::endl;    
}

template <int size>
void special_function()
{
     std::cout << "Called without specialization: " << size << std::endl;
}

template <>
void special_function<4>()
{
     std::cout << "dword" << std::endl;
}

template <>
void special_function<8>()
{
     std::cout << "qword" << std::endl;
}

int main()
{
     special_function<sizeof(int)>();
     return 0;
}

Then stored this code to sp.cpp. First,

g++ -Os sp.cpp -o sp
nm sp

and got this (note, I removed a bunch of symbols for readability):

0804879a T _Z13junk_functionv
080487b8 T _Z16special_functionILi4EEvv
080487f5 T _Z16special_functionILi8EEvv

Seems the two unused symbols are there. I also tried -O1, -O2, -O3, and got the same.

Next:

g++ -Os -fdata-sections -ffunction-sections sp.cpp -o sp -Wl,--gc-sections
nm sp

and got this:

0804875a T _Z16special_functionILi4EEvv

That's it. So it looks like you just need to pass the right arguments in to tell g++ to strip unused symbols. On mac, I guess they have the -dead_strip option, but I don't know why it doesn't work in g++ (even though it is mentioned in the man pages. Admittedly, I didn't dig into this, so there may be a fine print that I missed).

I think Visual C++'s linker strips by default when you link, but I didn't test. Maybe someone else can chime in.

Community
  • 1
  • 1
thang
  • 3,466
  • 1
  • 19
  • 31
  • 1
    I guess the specific question I have is, why is it generating the code at all, not why it doesn't strip the unused code once it's been generated. My understanding has always been that templates are generated when they are used. This is why you must (for example) include the entire template definition in any compilation unit that might instantiate the template. But here it appears as though template specializations are behaving like normally declared functions with possibly external linkage, that I could conceivably call with an appropriate extern declaration from another compilation unit. – 808140 Jan 31 '13 at 10:39
  • Working with CRTP (understand virtual without the virtual keyword) I use static_assert in order to know at compile time that I didn't override the base function that is emitting this assertion. If it's working fine with msvc, I can't use that with g++ (4.9), so I downgrade to an assert, it's a pain. – Jean Davy Dec 22 '15 at 09:03