Дифференцировать определения типов

#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. Но этот путь кажется довольно грубым…

  1. Существует много кода, уже специализированного для примитивных типов (например std::hash , ).
    Есть ли какой-нибудь способ сообщить компилятору что-то вроде «если что-то имеет специализацию для int , но не для моей оболочки, тогда просто используйте int специализацию»?
    Должен ли я в противном случае писать специализации для таких вещей, как std::hash ? Как насчет похожих шаблонных структур, которых нет std (например, в boost, Qt и т. Д.)?
  2. Должен ли я использовать неявный или явный конструктор и оператор приведения? Явные, конечно, безопаснее, но они могут сделать очень утомительным взаимодействие с существующим кодом и сторонними библиотеками, использующими C API.

Я более чем открыт для любых советов от тех, кто уже был там!

Комментарии:

1. Ну, C заставляет вас писать много шаблонного кода, чтобы делать что-то «способом C «. Это отстой, не так ли? Просто спросите себя, стоит ли того безопасность во время компиляции.

2. BOOST_STRONG_TYPEDEF

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.