#c #types #overloading #typedef
#c #типы #перегрузка #typedef
Вопрос:
Я пишу абстракцию C для библиотеки C. Библиотека C имеет несколько определений типов для идентификаторов, которые идентифицируют удаленные ресурсы:
typedef int color_id;
typedef int smell_id;
typedef int flavor_id;
// ...
color_id createColor( connection* );
void destroyColor( connection*, color_id );
// ...
Все эти определения типов, конечно, являются одним и тем же типом в глазах компилятора. Это проблема для меня, потому что я хотел бы перегружать функции и специализировать шаблоны, чтобы предложить хороший C дружественный API:
// can't do the following since `color_id`, `smell_id` and `int` are the same
std::ostreamamp; operator<<( std::ostreamamp;, color_id );
std::ostreamamp; operator<<( std::ostreamamp;, smell_id );
void destroy( connection*, color_id );
void destroy( connection*, smell_id );
// no static check can prevent the following
smell_id smell = createSmell( connection );
destroyColor( connection, smell ); // it's a smell, not a color!
- Как я могу дифференцировать эти идентификаторы, чтобы воспользоваться преимуществами безопасности типов и перегрузить / специализировать функции и классы?
Поскольку я не знаю другого способа, я думал о создании другого типа оболочки для каждого типа C. Но этот путь кажется довольно грубым…
- Существует много кода, уже специализированного для примитивных типов (например
std::hash
, ).
Есть ли какой-нибудь способ сообщить компилятору что-то вроде «если что-то имеет специализацию дляint
, но не для моей оболочки, тогда просто используйтеint
специализацию»?
Должен ли я в противном случае писать специализации для таких вещей, какstd::hash
? Как насчет похожих шаблонных структур, которых нетstd
(например, в boost, Qt и т. Д.)? - Должен ли я использовать неявный или явный конструктор и оператор приведения? Явные, конечно, безопаснее, но они могут сделать очень утомительным взаимодействие с существующим кодом и сторонними библиотеками, использующими C API.
Я более чем открыт для любых советов от тех, кто уже был там!
Комментарии:
1. Ну, C заставляет вас писать много шаблонного кода, чтобы делать что-то «способом C «. Это отстой, не так ли? Просто спросите себя, стоит ли того безопасность во время компиляции.
3. Почему бы вам просто не создать класс для каждого
color_id
,smell_id
, и т.д.?4. Вы можете проверить некоторые существующие оболочки OpenGL, которые решают именно эту проблему.
5. Использовать
enum class : int
Ответ №1:
Один класс-оболочка для управления ими всеми
Лучше всего было бы создать класс-оболочку, но, используя шаблоны, мы можем написать один шаблон класса-оболочки и использовать его для всех разных идентификаторов, просто назначив их разным экземплярам шаблона.
template<class ID>
struct ID_wrapper
{
constexpr static auto name() -> decltype(ID::name()) {
return ID::name();
}
int value;
// Implicitly convertible to `int`, for C operability
operator int() const {
return value;
}
};
Перегрузка std::hash
(только один раз)
Мы можем использовать в классе любые черты, которые мы хотим ID
, но я привел name()
в качестве примера. Поскольку ID_Wrapper
написано как шаблон, специализировать его для std::hash
других классов нужно только один раз:
template<class ID>
class std::hash<ID_wrapper<ID>> : public std::hash<int>
{
public:
// I prefer using Base to typing out the actual base
using Base = std::hash<int>;
// Provide argument_type and result_type
using argument_type = int;
using result_type = std::size_t;
// Use the base class's constructor and function call operator
using Base::Base;
using Base::operator();
};
Распечатка идентификатора с его именем
Если вы хотите, мы могли бы также специализироваться operator<<
, но ID_wrapper
int
в любом случае неявно преобразуется в:
template<class ID>
std::ostreamamp; operator<<(std::ostreamamp; stream, ID_Wrapper<ID> id) {
stream << '(' << ID_Wrapper<ID>::name() << ": " << id.value << ')';
return stream;
}
Как только у нас это будет, мы просто напишем класс признаков для каждого типа идентификатора!
struct ColorIDTraits {
constexpr static const char* name() {
return "color_id";
}
};
struct SmellIDTraits {
constexpr static const char* name() {
return "smell_id";
}
};
struct FlavorIDTraits {
constexpr static const char* name() {
return "flavor_id";
}
};
Объединение всего этого вместе
Затем мы можем typedef
использовать ID_wrapper:
using color_id = ID_wrapper<ColorIDTraits>;
using smell_id = ID_wrapper<SmellIDTraits>;
using flavor_id = ID_wrapper<FlavorIDTraits>;
Ответ №2:
Используйте BOOST_STRONG_TYPEDEF, как прокомментировал @MooingDuck.