This is meant to go in a vulkan.hpp
-like strongly-typed wrapper for a pure C library.
A wrapped enum provides back and forth implicit conversion between itself and the enum, while otherwise behaving as an enum class
would.
Wrapper utility:
#include <type_traits>
template <typename EnumT>
struct enum_wrapper {
using enum_type = EnumT;
using wrapped_type = enum_wrapper<EnumT>;
enum_wrapper() noexcept = default;
~enum_wrapper() = default;
// Implicitly castable from enum_type
constexpr enum_wrapper(enum_type val) noexcept : _value(val) {}
// Implicitly castable to enum_type
constexpr operator enum_type() const noexcept { return _value; }
// Because of the implicit conversion, we need to intercept all comparisons
// in order to prevent comparing against an unrelated enum.
template <typename T>
constexpr bool operator<(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value < rhs;
}
template <typename T>
constexpr bool operator<=(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value <= rhs;
}
template <typename T>
constexpr bool operator==(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value == rhs;
}
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
constexpr bool operator!=(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value != rhs;
}
template <typename T>
constexpr bool operator>(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value < rhs;
}
template <typename T>
constexpr bool operator>=(T rhs) const noexcept {
static_assert(is_comparable<T>());
return _value <= rhs;
}
private:
enum_type _value;
template <typename T>
static constexpr bool is_comparable() {
return std::is_same_v<std::decay_t<T>, enum_type> || std::is_same_v<std::decay_t<T>, enum_wrapper>;
}
};
template <typename LhsT, typename EnumT>
constexpr bool operator<(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs > lhs;
}
template <typename LhsT, typename EnumT>
constexpr bool operator<=(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs >= lhs;
}
template <typename LhsT, typename EnumT>
constexpr bool operator==(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs == lhs;
}
template <typename LhsT, typename EnumT>
constexpr bool operator!=(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs != lhs;
}
template <typename LhsT, typename EnumT>
constexpr bool operator>(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs < lhs;
}
template <typename LhsT, typename EnumT>
constexpr bool operator>=(LhsT lhs, enum_wrapper<EnumT> rhs) noexcept {
return rhs <= lhs;
}
}
Wrapping:
extern "C" {
enum lib_status {
LIB_STATUS_OK = 0,
LIB_STATUS_BAD_ARGUMENT = 1,
};
typedef enum lib_status lib_status;
enum lib_color {
LIB_COLOR_RED = 0,
LIB_COLOR_GREEN = 1,
LIB_COLOR_BLUE = 2,
};
typedef enum lib_color lib_color;
lib_status lib_foo(lib_color color);
}
namespace lib {
// <insert copy of lib_color documentation>
struct color : detail::enum_wrapper<lib_color> {
using enum_wrapper::enum_wrapper;
color(enum_wrapper rhs) : enum_wrapper(rhs) {}
static constexpr wrapped_type red{LIB_COLOR_RED};
static constexpr wrapped_type green{LIB_COLOR_GREEN};
static constexpr wrapped_type blue{LIB_COLOR_BLUE};
};
// <insert copy of lib_status documentation>
struct status : detail::enum_wrapper<lib_status> {
using enum_wrapper::enum_wrapper;
status(enum_wrapper rhs) : enum_wrapper(rhs) {}
static constexpr wrapped_type ok{LIB_STATUS_OK};
static constexpr wrapped_type {LIB_STATUS_BAD_ARGUMENT};
};
inline status foo(color in_col) {
return lib_foo(in_col);
}
}
Using a wrapped enum:
static_assert(std::is_trivial_v<lib::status>);
int main() {
lib::status success = lib::status::ok;
success = LIB_STATUS_OK;
success = lib::foo(lib::color::red);
success = lib_foo(LIB_COLOR_RED);
// if(success == LIB_COLOR_RED) {} // DOES NOT COMPILE
// can switch
switch(success) {
case lib::status::ok: break;
case lib::status::bad_argument: break;
}
// Missing cases are caught: DOES NOT COMPILE
// switch(success) {
// case lib::status::ok: break;
// }
// These might look undesirably loose at a glance.
// However, this allows using the c++ wrapper with 3rd-party
// libraries that only implements a C api. e.g. using glfw()
// alongside vulkan.hpp
lib_foo(lib::color::green); // Can call C api with wrapper
lib::foo(LIB_COLOR_RED); // Can still use the C enum in the C++ api
// lib::foo(LIB_STATUS_OK); // DOES NOT COMPILE
}
See it all on godbolt: https://gcc.godbolt.org/z/5d6cTPPnb
I'm looking for general feedback and/or potential pitfalls users of the wrapped library could run into.