Дело было так: есть некий объемный и сложный шаблон класса-контейнера. Для тестирования было создано приложение с юнит-тестами на базе Google.Test. В состав этого приложения входит порядка 30 (тридцати) .cpp-файлов. В некоторых из них происходит следующее:
|
namespace { template<typename T> struct test_traits : public my_container::default_traits<T> { static constexpr std::size_t key_size = 3; }; } /* namespace anonymous */ TEST(my_container, some_test) { my_container::my_map<int, test_traits> map; ... // какие-то действия с map. } |
Т.е. суть в том, что в десятке .cpp-файлов есть анонимные пространства имен, в каждом из которых определяется шаблон класса с именем test_traits. Затем этот шаблон используется для инстанцирования класса-контейнера.
Все это работало до тех пор, пока не был добавлен еще один .cpp-файл, в котором было практически тоже самое:
|
namespace { template<typename T> struct test_traits : public my_container::default_traits<T> { static constexpr std::size_t key_size = 3; static constexpr my_container::mode use_mode = my_container::mode::versioned; }; } /* namespace anonymous */ TEST(my_container, some_test_versioned) { my_container::my_map<int, test_traits> map; ... // какие-то действия с map. } |
И вот тут-то в some_test_versioned с map стали происходит странные вещи: возникали segmentation faults там, где их быть не должно было. Попытки отладить код приводили к тому, что отладчик показывал, что отрабатывают не те ветки if-ов. А отладочные печати содержали совсем не те значения, которые должны были бы быть.
Было полное ощущение, что GCC сошел с ума.
Проект, в рамках которого все это делается, собирается VC++ под Windows и GCC под Linux-ом. Под Linux-ами используются GCC 12 и 13. Конкретно я работаю с GCC 13, но проверил и под GCC 12. Сам проект уже не очень маленький, плюс подтягивает кучу зависимостей разного калибра (включая Folly и Abseil). Все это к тому, что мероприятие по перекомпиляции проекта под какой-то свежий GCC или clang -- это попытка с негарантированным результатом. Может повезти, а может и нет.
Под Windows проверил, там ничего подобного нет, все работает как и положено. А вот под Linux-овым GCC -- проблемы.
В итоге подумал о том, что GCC воспринимает все мои test_traits как нарушение ODR и я наступаю на грабли UB. Поэтому переименовал test_traits так, чтобы во всех .cpp-файлах имена оказались уникальными, даже не смотря на то, что живут они в анонимных пространствах имен.
После этого все описанные выше магические проблемы разом исчезли.
Есть у меня сильное подозрение, что это таки был баг в GCC. Поскольку, если мне не изменяет склероз, все, что определяется внутри анонимного пространство имен, должно быть абсолютно уникальным. В том числе это касается и шаблонов.
Но на 100% не уверен. Может быть здесь дело еще и в том, что у my_container::map есть шаблонный параметр шаблона, т.е.:
|
namespace my_container { template<typename T, template<typename> class Traits> class map { ... }; } /* namespace my_container */ |
Поэтому его параметризация в тесте идет не конкретными типами, а шаблоном:
|
TEST(my_container, some_test_versioned) { my_container::my_map< int, // Это конкретный тип. test_traits // А это шаблон, который развернется в конкретный // тип уже внутри map. > map; ... // какие-то действия с map. } |
И вот именно из-за этого ODR и нарушается. Но это не точно. И я даже не знаю в какую часть C++ного стандарта заглядывать, чтобы выяснить кто именно был не прав.