Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do compiler options impact selection of template implementation?

Tags:

c++

templates

g++

Depending on whether I compile with -O3 or without optimization the compiler does not select the same function-template-instanciation. Using gcc (Debian 8.3.0-6) 8.3.0.

Due to an oversight I have a default implementation in a function template declaration:

#pragma once

#include <iostream>

template <int>
void func() { std::cerr << "default impl\n"; } // normally no impl here

And their specialisations:

#include "func.h"

template <>
void func<1>()
{
    std::cerr << "special 1\n";
}

template <>
void func<2>()
{
    std::cerr << "special 2\n";
}

And the main-function.

#include "func.h"

int main(void)
{
    func<1>();
    func<2>();

    return 0;
}

Compiling and running g++ -Wall func.cpp main.cpp -o main && ./main gives:

special 1
special 2

Using optimizations g++ -O3 -Wall func.cpp main.cpp -o main && ./main gives:

default impl
default impl

Is this expected? Is the code triggering an unexpected behavior I'm not aware of?

Thanks to @NathanOliver from the comments who made a Wandbox. Compiling with or without optimizations shows the different output.

like image 651
Patrick B. Avatar asked Jul 03 '19 13:07

Patrick B.


2 Answers

Your code is ill-formed, no diagnostic required. So different behaviors at different optimization levels are possible.

[temp.expl.spec]

6 If a template, a member template or a member of a class template is explicitly specialized then that specialization shall be declared before the first use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit specialization that is declared but not defined.

The function template is specialized in one TU, but the other doesn't have a specialization declaration available. It's quite likely an aggressive optimizer chooses the implicit instantiation (that is available inline) instead of finding the one you created elsewhere. The solution is to declare that your specialization exists in the header.

like image 109
StoryTeller - Unslander Monica Avatar answered Oct 19 '22 03:10

StoryTeller - Unslander Monica


You have undefined behaviour because of ODR issues.

ODR says that there should be only one definition for each symbol. Inline and template functions can multiple definitions but must have the same implementation, token by token. No diagnostic required if this rule is broken.

When compiling your example, the compiler will instantiate your function. Look at this:

template <int>
void func() { std::cerr << "default impl\n"; } // normally no impl here

int main(void)
{
    func<1>();
    func<2>();

    return 0;
}

This is what the compiler sees. It cannot see other cpp files. The compiler will instantiate the templates and create additional definition for your functions.

Then your other cpp file will provide another definition that is different.

The solution to this is to forward declare the specializations in your header:

template<> void func<1>();
template<> void func<2>();

This will tell the compiler that the specializations are declared elsewhere, and not to instantiate the default one.

like image 45
Guillaume Racicot Avatar answered Oct 19 '22 04:10

Guillaume Racicot