В С++ есть отличная штука под названием using. Позволяет вводить удобные псевдонимы для сложночитаемых имен типов или же прикрывать тонкой завесой детали реализации.
Но, к сожалению, using не может сделать совершенно новый тип. Поэтому если у вас есть что-то вроде:
using type_a = ...; using type_b = ...; void do_something(type_a v) {...} void do_something(type_b v) {...} |
То вы внезапно можете обнаружить, что ваш код перестал компилироваться потому, что type_a и type_b оказались синонимами для одного и того же типа.
Например, раньше было:
using small_data = std::map<some_key, some_value>; using large_data = std::unordered_map<special_key, some_value>; using type_a = small_data::iterator; using type_b = large_data::iterator; |
А в один прекрасный момент стало:
using special_key = some_key; using small_data = std::map<some_key, some_value>; using large_data = std::map<special_key, some_value>; |
И все 🙁
Типы type_a и type_b оказались одинаковыми.
Недавно в очередной раз наступил на подобные грабли, но немного в другом контексте. Было что-то вроде:
namespace processing { class processor {...}; template<typename Data> void handle(const processor & how, const Data & what) { // Должна быть найдена подходящая функция за счет ADL. apply(what, how); } } // namespace processing namespace data_type_a { struct data {...}; void apply(const data & what, const processing::processor & how) {...} } // namespace data_type_a namespace data_type_b { struct data {...}; void apply(const data & what, const processing::processor & how) {...} } // namespace data_type_b |
И т.д.
Т.е. смысл в том, что в конкретном пространстве имен data_type_X должна быть функция apply, который компилятор посредством ADL находит для вызова внутри processing::process.
Все шло хорошо до момента, пока не появились data_type_i и data_type_j, в которых тип data был определен через using:
namespace data_type_i { using data = std::map<...>; void apply(const data & what, const processing::processor & how) {...} } // namespace data_type_i namespace data_type_j { using data = std::vector<...>; void apply(const data & what, const processing::processor & how) {...} } // namespace data_type_j |
И вот когда эти типы начали отдавать в processing::process, то код перестал компилироваться. Причем далеко не сразу удалось понять, почему ни одна из определенных в правильных пространствах имен apply не выбиралась компилятором как подходящая.
А дело в том, что если заменить псевдонимы, то получались функции вида:
void apply(const std::map<...> &, const processing::processor&); void apply(const std::vector<...> &, const processing::processor&); |
И естественно, что ADL не мог их найти ни в пространстве имен std, ни в processing.
Было больно, т.к. пришлось отказаться от очень красивого способа привязки специфических данных к их обработчику 😪
В очередной раз захотелось, чтобы using в С++ мог работать как strong typedef. Чтобы можно было написать что-то вроде:
namespace data_type_i { using(new) data = std::map<int, std::string>; } // namespace data_type_i |
И чтобы компилятор начал считать, что data и std::map<int, std::string> теперь разные типы. И что тип data теперь принадлежит пространству имен data_type_i, а не std.
PS. В свете добавления в C++ рефлексии может оказаться, что наколхозить какой-то нестандартый strong_typedef_for, типа:
namespace data_type_i { using data = my::strong_typedef_for< std::map<int, std::string> >; } // namespace data_type_i |
через рефлексию будет быстрее и проще, чем дождаться появления using(new) в стандарте. Обычная традиция C++: если что-то можно собрать своими руками дендро-фекальным методом, то включать в стандарт удобный и нормальный вариант никто не будет.