我有一个相当简单的变体类,它支持一组预定义的类型,并提供一个枚举来指示当前哪些可用类型处于活动状态。 像这样:
class variant
{
enum class type { integer, real, string, etc };
type active_type() const;
/* ... */
};
我想将类变成一个模板,其中支持的类型作为模板参数提供:
template <typename... T>
class variant
{
const std::type_info& active_type() const; // logical, but can't switch on it
/* ... */
};
我用来捕获错误的一个关键功能是,我可以switch
活动类型,编译器将在遗漏任何可能的情况时发出警告。 使用上述设计(或使用boost::variant
(无法做到这一点。
我的问题是,有没有办法自动生成一个枚举数与参数包中的参数数相同的枚举?
枚举的实际名称/值无关紧要,因为它们可以隐藏在用于将类型映射到正确枚举的 constexpr 函数后面。 我可以想象这样的最终用法:
template <typename... T>
class variant
{
enum class type { T... }; // magic here
// specializations provided to map T into type (for use in case labels)
template <typename T>
static constexpr type type_enum();
type active_type() const;
/* ... */
};
typedef variant<int, float, std::string> myvar;
myvar var;
switch (var.active_type())
{
case myvar::type_enum<int>(): // static constexpr function
...
break;
case myvar::type_enum<float>():
...
break;
} // warning: enumeration for string not handled in switch
这个问题已经两年了,但由于没有答案,而且我在其他地方找不到解决方案,这是我解决问题的方法。
第一个明显的问题是具有匹配参数数量的枚举类型(让编译器进行切换检查(。我看到的唯一方法是一系列模板专业化。
template <typename Tag, size_t size> struct Enum;
template <typename Tag> struct Enum<Tag, 0> {
enum class Type { };
};
template <typename Tag> struct Enum<Tag, 1> {
enum class Type {
VALUE0
};
};
template <typename Tag> struct Enum<Tag, 2> {
enum class Type {
VALUE0, VALUE1
};
};
template <typename Tag> struct Enum<Tag, 3> {
enum class Type {
VALUE0, VALUE1, VALUE2
};
};
template <typename Tag> struct Enum<Tag, 4> {
enum class Type {
VALUE0, VALUE1, VALUE2, VALUE3
};
};
是的,它需要一些手动输入,但这不是一个大问题,请记住,类型数量通常是有限的,并且可以根据需要添加新的专业化(即该方法是静态安全的(。Tag 参数用于区分具有相同数量值的枚举。
其次,我们需要一些递归模板魔术来枚举类型:
template <typename EnumType, typename Type, typename... Types> struct TypeInfo;
template <typename EnumType, typename Type, typename... Types> struct TypeInfo<EnumType, Type, Type, Types...> {
static const EnumType value = EnumType::VALUE0;
};
template <typename EnumType, typename Type, typename FirstType, typename... Types> struct TypeInfo<EnumType, Type, FirstType, Types...> {
static const EnumType value = static_cast<EnumType>(static_cast<typename std::underlying_type<EnumType>::type>(TypeInfo<EnumType, Type, Types...>::value) + 1);
};
最后是一个将所有部分组合在一起的类:
template <typename Tag, typename... Ts> class TypedEnum {
private:
struct InternalTag;
public:
static const size_t NUM_TYPES = sizeof...(Ts);
typedef typename Enum<InternalTag, NUM_TYPES>::Type Type;
template <typename T> static constexpr decltype(TypeInfo<Type, T, Ts...>::value) getValue() { // SFINAE guard
return TypeInfo<Type, T, Ts...>::value;
}
};
瞧!你可以切换!(用叮当声测试(
struct Tag0;
struct Tag1;
typedef TypedEnum<Tag0, int, float, char> Enum0;
typedef TypedEnum<Tag0, bool, float, char> Enum1; // Incompatible with Enum0!
Enum0::Type tpy = Enum0::getValue<char>(); // 2
switch(tpy) {
case Enum0::getValue<int>():
break;
case Enum0::getValue<float>():
break;
case Enum0::getValue<char>():
break;
}
功能摘要:
- 静态拒绝不在枚举中的类型
- 静态警告缺少开关外壳
- 包中不同标签和/或参数的枚举类型不兼容
缺少的功能:
- 需要一些模板魔术来拒绝具有重复项的参数包
在可变参数模板中获取类型的索引,并将其用作枚举值。这样,您无需复制不同类型数量的所有代码。这不会处理列表中的重复类型,但索引将是可变参数列表中的第一个匹配项。如果需要重复检测,可以使用std::is_same
添加它,并以类似于IndexOf
的方式循环访问每种类型的所有类型。
#include <type_traits>
using std::size_t;
template <size_t Idx, class Q, class... Ts>
struct IndexOfImpl;
// base case
template <size_t Idx, class Q>
struct IndexOfImpl<Idx, Q>{};
template <size_t Idx, class Q, class T>
struct IndexOfImpl<Idx, Q, T>
: public std::conditional_t<
std::is_same<Q, T>::value, // Is Query the same as T?
// if so
std::integral_constant<size_t, Idx>, // inheret from Idx
void> // type not found
{
};
template <size_t Idx, class Q, class T, class... Ts>
struct IndexOfImpl<Idx, Q, T, Ts...>
: public std::conditional_t<
std::is_same<Q, T>::value, // Is Query the same as T?
// if so
std::integral_constant<size_t, Idx>, // inheret from Idx
IndexOfImpl<Idx + 1, Q, Ts...>> // else try the trailing types
{
};
// Initial case at Idx 0
template <class Q, class... Ts>
struct IndexOf : public IndexOfImpl<0, Q, Ts...>
{
};
// Base case
template <class Q>
struct IndexOf<Q>
{
};
// Store the type list in Ts...
template <class... Ts>
struct TypeEnum {
// Get index/enum value of type T
template <class T>
static constexpr decltype(IndexOf<T, Ts...>::value) getValue() {
return IndexOf<T, Ts...>::value;
}
};
template <>
struct TypeEnum<> {};
struct Unknown;
int main() {
using Tags = TypeEnum<Unknown, int, float, long long, unsigned int, double>;
size_t tag = Tags::getValue<double>();
switch(tag) {
case Tags::getValue<int>(): return 0;
case Tags::getValue<float>(): return 0;
case Tags::getValue<long long>(): return 0;
case Tags::getValue<unsigned int>(): return 0;
case Tags::getValue<double>(): return 0;
default: return 1;
}
}
在编译器资源管理器上