Отсутствие конфликта имён при инстанцировании шаблона в разных модулях - почему?

Задам несколько необычный вопрос. В том смысле, что обычно ожидают, что код заработает, и спрашивают, почему он не работает. У меня же получилось наоборот: я ожидал, что будет ошибка, но всё слинковалось и работает.

Как известно, вся реализация шаблонного класса обретает плоть только тогда, когда он непосредственно инстанциируется. Т.е., когда в cpp-файле, допустим, объявляется переменная данного класса с заданными параметрами шаблона. Причём, все методы класса, а также его статические поля размещаются в том же объектном модуле, в который компилируется cpp-файл. Но отсюда следует и проблема: если cpp-файлов, в которых инстанциируется шаблон с одинаковыми параметрами, несколько, то каждый скомпилированный из этих файлов объектный модуль будет иметь по копии реализации класса. А следовательно, при линковке должна возникнуть ошибка - конфликт имён.

Проверим на практике. Вот h-файл с шаблоном:

/*
 * File:    common.h
 */

#ifndef common_h_included_
#define common_h_included_


//tested template
template <class T>
class CTestTemplate
{
    static int  s_num;

public:
    static void print();
};


//tested template static method implementation
template <class T> void CTestTemplate<T>::print()
{
    printf("sizeof(T) is %zu, s_num==%d\n", sizeof(T), s_num);
}

//tested template static field implementation
template <class T> int CTestTemplate<T>::s_num = sizeof(T)*2 + 1;


//functions prototype
void call_func(void (*func)());
void test_func2();


#endif //common_h_included_

Вот файл main.cpp:

/*
 * FILE: main.cpp
 */

#include <stdio.h>
#include "common.h"

int main()
{
    CTestTemplate<double> t;  //instatntiating template in module main.o

    //test 1
    printf("*** test-1 ***\n");
    call_func(CTestTemplate<double>::print);

    //test 2
    test_func2();

    //test 3
    printf("*** test-3 ***\n");
    call_func(CTestTemplate<double>::print);

    return 0;
}

Вот cpp-файл со вторым инстанциированием шаблона:

/*
 * FILE: module2.cpp
 */

#include <stdio.h>
#include "common.h"


void test_func2()
{
    CTestTemplate<double> t; //instatntiating template in module module2.o

    //some testing code
    printf("*** test-2 ***\n");
    call_func(CTestTemplate<double>::print);
}

Вот файл с фанкцией call_func(), чтобы было интереснее (и чтобы компилятор гарантированно не сделал искомый метод inline):

/*
 * FILE: call_func.cpp
 */

#include <stdio.h>
#include "common.h"

void call_func(void (*func)())
{
    printf("Calling function...\n");
    func();
}

Компилируем, собираем и... всё успешно собирается и работает! Ошибок у линкера не возникает! И тут бы задаться другим вопросом: а стоит ли по этому поводу беспокоится, писать на SO, ведь работает - значит всё хорошо. Но я так не могу, мне надо точно знать, почему нет конфликта на линкере? Ведь когда-то он был. Несколько лет назад из-за этого я не смог собрать проет, из-за чего тогда решил отказаться от шаблонов, за исключением полностью инлайновых. Неужели разработчики языка (вернее, компилятора) всё починили? Говорит ли что-нибудь по-этому поводу стандарт? Гарантируется ли, что конфликта линковки при работе с шаблонами не возникнет, если использовать любой другой современный компилятор, помимо GCC (да и насколько гарантируется это в самом GCC)?

P.S. Копии реализации присутствуют в обоих модулях: и в main.o, и в module2.o - это я проверил. Похоже, линкер понимает, что это копии, и в финальный экзешник помещает один экземпляр.


Ответы (0 шт):