值和类型的简明双向静态 1:1 映射
Concise bidirectional static 1:1 mapping of values and types
我将从我想象如何使用我想要创建的代码开始。它不必完全像这样,但它是我在标题中所说的“简洁”的一个很好的例子。在我的例子中,它是一个类型到相关枚举值的映射。
struct bar : foo<bar, foo_type::bar> { /* ... */ };
// \_/ \___________/
// ^ Type ^ Value
理想情况下,这应该做的是自动注册 foo
的第一个模板参数、类型和第二个值之间的双向映射,只需使用继承语法和适当的模板参数,所以我稍后可以执行下面示例中的操作。
foo_type value = to_value<bar>; // Should be foo_type::bar
using type = to_type<foo_type::bar>; // Should be bar
我知道我可以为每个 type-value 对手动编写两个模板专业化来执行此操作,但我想知道它是否可以比不使用宏更简单。
我已经尝试过的是...
- 特化模板别名以编写更少的代码来生成特化。显然在当前的 C++ 版本 (17/20) 中是不可能的。
- 专门化继承的模板成员类型。
struct foo_base
{
template<typename T>
struct to_value
{};
template<foo_type E>
struct to_type
{};
};
template<typename T, foo_type E>
struct foo : public foo_base
{
template<>
struct to_value<T>
{
static constexpr auto value = E;
};
template<>
struct to_type<E>
{
using type = T;
};
};
然后它的使用方式与我在开头介绍的类似。
foo_type value = foo_base::to_value<bar>::value; // Should be foo_type::bar
using type = foo_base::to_type<foo_type::bar>::type; // Should be bar
但它在 MSVC 上失败并出现以下错误。
explicit specialization; 'foo_base::to_value' has already been instantiated
'foo_base::to_value': cannot specialize template in current scope
我觉得如果没有明确的手动专门化可能无法实现,但 C++17 允许许多令人惊讶的基于模板的黑客攻击,所以在我放弃这个想法之前想与更有经验的人确认。
正如@yeputons 所说,友元注入可以提供帮助。这是一个令人毛骨悚然的功能,我不能说我完全理解它是如何工作的,但就是这样。
#include <iostream>
#include <type_traits>
template <typename T>
struct tag {using type = T;};
template <typename T>
struct type_to_enum_friend_tag
{
friend constexpr auto adl_type_to_enum(type_to_enum_friend_tag);
};
template <auto E>
struct enum_to_type_friend_tag
{
friend constexpr auto adl_enum_to_type(enum_to_type_friend_tag);
};
namespace impl
{
// Would've used `= delete;` here, but GCC doesn't like it.
void adl_type_to_enum() {}
void adl_enum_to_type() {}
}
template <typename T>
constexpr auto type_to_enum_helper()
{
// Make sure our ADL works even if some stray
// identifier named `adl_type_to_enum` is visible.
using impl::adl_type_to_enum;
return adl_type_to_enum(type_to_enum_friend_tag<T>{});
}
template <typename T>
inline constexpr auto type_to_enum = type_to_enum_helper<T>();
template <auto E>
constexpr auto enum_to_type_helper()
{
// Make sure our ADL works even if some stray
// identifier named `adl_type_to_enum` is visible.
using impl::adl_enum_to_type;
return adl_enum_to_type(enum_to_type_friend_tag<E>{});
}
template <auto E>
using enum_to_type = typename decltype(enum_to_type_helper<E>())::type;
template <typename T, auto E>
struct foo
{
friend constexpr auto adl_type_to_enum(type_to_enum_friend_tag<T>)
{
return E;
}
friend constexpr auto adl_enum_to_type(enum_to_type_friend_tag<E>)
{
return tag<T>{};
}
};
enum class foo_type {bar = 42};
struct bar : foo<bar, foo_type::bar>
{
void say() {std::cout << "I'm bar!\n";}
};
int main()
{
std::cout << int(type_to_enum<bar>) << '\n'; // 42
enum_to_type<foo_type::bar>{}.say(); // I'm bar!
}
它似乎适用于 GCC、Clang 和 MSVC。
我正在使用 auto
模板参数,因此您可以将不同类型映射到不同枚举中的常量,甚至映射到普通整数。限制它只接受一个特定的枚举应该很容易,并且作为练习留给 reader.
当然,对于类型到枚举的映射,您可以简单地向 foo
添加一个 static constexpr
成员变量。但是我不知道有什么好的方法可以替代友元注入来实现枚举到类型的映射。
@HolyBlackCat 的回答很棒。类型到枚举可以通过比 ADL hackery 更简单的方式实现,所以我尝试将枚举到类型位提炼到最低限度:
template <auto E>
struct adl_to_type
{
friend auto foo_type_to_type(adl_to_type);
};
template<typename T, foo_type E>
struct foo
{
friend auto foo_type_to_type(adl_to_type<E>) { return (T*)nullptr; };
};
template <foo_type E>
using to_type = std::remove_pointer_t<decltype(foo_type_to_type(adl_to_type<E>{}))>;
int main()
{
to_type<foo_type::bar>{}.say();
return 0;
}
它仍然让我印象深刻。 auto
return 类型在这里绝对至关重要。即使在 foo
中将其更改为 T*
也会产生编译错误。我也尝试摆脱 adl_to_type
并改用 integral_constant
,但似乎将 foo_type_to_type
声明为朋友函数 inside 用于解决 ADL 是这里的关键。
我将从我想象如何使用我想要创建的代码开始。它不必完全像这样,但它是我在标题中所说的“简洁”的一个很好的例子。在我的例子中,它是一个类型到相关枚举值的映射。
struct bar : foo<bar, foo_type::bar> { /* ... */ };
// \_/ \___________/
// ^ Type ^ Value
理想情况下,这应该做的是自动注册 foo
的第一个模板参数、类型和第二个值之间的双向映射,只需使用继承语法和适当的模板参数,所以我稍后可以执行下面示例中的操作。
foo_type value = to_value<bar>; // Should be foo_type::bar
using type = to_type<foo_type::bar>; // Should be bar
我知道我可以为每个 type-value 对手动编写两个模板专业化来执行此操作,但我想知道它是否可以比不使用宏更简单。
我已经尝试过的是...
- 特化模板别名以编写更少的代码来生成特化。显然在当前的 C++ 版本 (17/20) 中是不可能的。
- 专门化继承的模板成员类型。
struct foo_base
{
template<typename T>
struct to_value
{};
template<foo_type E>
struct to_type
{};
};
template<typename T, foo_type E>
struct foo : public foo_base
{
template<>
struct to_value<T>
{
static constexpr auto value = E;
};
template<>
struct to_type<E>
{
using type = T;
};
};
然后它的使用方式与我在开头介绍的类似。
foo_type value = foo_base::to_value<bar>::value; // Should be foo_type::bar
using type = foo_base::to_type<foo_type::bar>::type; // Should be bar
但它在 MSVC 上失败并出现以下错误。
explicit specialization; 'foo_base::to_value' has already been instantiated
'foo_base::to_value': cannot specialize template in current scope
我觉得如果没有明确的手动专门化可能无法实现,但 C++17 允许许多令人惊讶的基于模板的黑客攻击,所以在我放弃这个想法之前想与更有经验的人确认。
正如@yeputons 所说,友元注入可以提供帮助。这是一个令人毛骨悚然的功能,我不能说我完全理解它是如何工作的,但就是这样。
#include <iostream>
#include <type_traits>
template <typename T>
struct tag {using type = T;};
template <typename T>
struct type_to_enum_friend_tag
{
friend constexpr auto adl_type_to_enum(type_to_enum_friend_tag);
};
template <auto E>
struct enum_to_type_friend_tag
{
friend constexpr auto adl_enum_to_type(enum_to_type_friend_tag);
};
namespace impl
{
// Would've used `= delete;` here, but GCC doesn't like it.
void adl_type_to_enum() {}
void adl_enum_to_type() {}
}
template <typename T>
constexpr auto type_to_enum_helper()
{
// Make sure our ADL works even if some stray
// identifier named `adl_type_to_enum` is visible.
using impl::adl_type_to_enum;
return adl_type_to_enum(type_to_enum_friend_tag<T>{});
}
template <typename T>
inline constexpr auto type_to_enum = type_to_enum_helper<T>();
template <auto E>
constexpr auto enum_to_type_helper()
{
// Make sure our ADL works even if some stray
// identifier named `adl_type_to_enum` is visible.
using impl::adl_enum_to_type;
return adl_enum_to_type(enum_to_type_friend_tag<E>{});
}
template <auto E>
using enum_to_type = typename decltype(enum_to_type_helper<E>())::type;
template <typename T, auto E>
struct foo
{
friend constexpr auto adl_type_to_enum(type_to_enum_friend_tag<T>)
{
return E;
}
friend constexpr auto adl_enum_to_type(enum_to_type_friend_tag<E>)
{
return tag<T>{};
}
};
enum class foo_type {bar = 42};
struct bar : foo<bar, foo_type::bar>
{
void say() {std::cout << "I'm bar!\n";}
};
int main()
{
std::cout << int(type_to_enum<bar>) << '\n'; // 42
enum_to_type<foo_type::bar>{}.say(); // I'm bar!
}
它似乎适用于 GCC、Clang 和 MSVC。
我正在使用 auto
模板参数,因此您可以将不同类型映射到不同枚举中的常量,甚至映射到普通整数。限制它只接受一个特定的枚举应该很容易,并且作为练习留给 reader.
当然,对于类型到枚举的映射,您可以简单地向 foo
添加一个 static constexpr
成员变量。但是我不知道有什么好的方法可以替代友元注入来实现枚举到类型的映射。
@HolyBlackCat 的回答很棒。类型到枚举可以通过比 ADL hackery 更简单的方式实现,所以我尝试将枚举到类型位提炼到最低限度:
template <auto E>
struct adl_to_type
{
friend auto foo_type_to_type(adl_to_type);
};
template<typename T, foo_type E>
struct foo
{
friend auto foo_type_to_type(adl_to_type<E>) { return (T*)nullptr; };
};
template <foo_type E>
using to_type = std::remove_pointer_t<decltype(foo_type_to_type(adl_to_type<E>{}))>;
int main()
{
to_type<foo_type::bar>{}.say();
return 0;
}
它仍然让我印象深刻。 auto
return 类型在这里绝对至关重要。即使在 foo
中将其更改为 T*
也会产生编译错误。我也尝试摆脱 adl_to_type
并改用 integral_constant
,但似乎将 foo_type_to_type
声明为朋友函数 inside 用于解决 ADL 是这里的关键。