问题描述
我将以我想像的方式使用我想创建的代码开始。不一定非要这样,但这是我所说的简洁的一个很好的例子。在标题中。在我的情况下,它是将类型映射到相关的枚举值。
I'm going to start with how I imagine using the code I'd like to create. It doesn't have to be exactly like this but it's a good example of what I mean by "concise" in the title. In my case it's mapping of a type to a related enumeration value.
struct bar : foo<bar, foo_type::bar> { /* ... */ };
// \_/ \___________/
// ^ Type ^ Value
什么理想情况下,这应该使用继承语法和适当的模板自动注册 foo
的第一个模板参数和类型以及第二个值之间的双向映射。参数,以便以后可以执行下面的示例。
What this should ideally do is an automatic registration of a bidirectional mapping between the first template parameter of foo
, a type, and second, a value, just with the inheritance syntax and proper template parameters, so that I can later do what's in the example below.
foo_type value = to_value<bar>; // Should be foo_type::bar
using type = to_type<foo_type::bar>; // Should be bar
我知道我可以为每个类型-值对手动编写两个模板专业化代码,但是我我想知道如果不使用宏,它是否会比那么少乏味。
I know I could manually write two template specializations per type-value pair to do this but I'm wondering if it could be less tedious than that without using macros.
我已经尝试过的是...
What I tried already is...
- 专业化模板别名,以减少编写专业代码的代码。在当前的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;
};
};
然后将其与我在开始时介绍的内容类似地使用。
It would then be used similarily to what I presented at the beginning.
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上出现以下错误。
But it fails with errors below on MSVC.
'foo_base :: to_value':无法在当前范围内专门化模板
'foo_base::to_value': cannot specialize template in current scope
我觉得如果没有明确的手动专业知识可能无法实现,但是C ++ 17允许很多令人惊讶的基于模板的黑客攻击,因此想在我放弃这个想法之前先与经验丰富的人确认一下。
I feel like it might not be doable without explicit manual specializations, but C++17 allows a lot of surprising template based hacks, so want to confirm with more experienced people before I drop the idea.
推荐答案
正如@yeputons所说,朋友注入在这里可以提供帮助。这是一个令人毛骨悚然的功能,我不能说我完全理解它的工作原理,但是在这里就行了。
As @yeputons said, friend-injection can help here. It's a spooky feature, and I can't say I fully understand how it works, but here it goes.
#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上使用。
It appears to work on both GCC, Clang, and MSVC.
我正在使用 auto
template参数,因此您可以将不同的类型映射到来自不同枚举的常量,甚至映射到普通整数。将其约束为仅接受一个特定的枚举应该很容易,并且留给读者练习。
I'm using an auto
template parameter, so you can map different types to constants from different enums, or even to plain integers. Constraining this to accept only a single specific enum should be easy, and is left as an exercise to the reader.
当然,对于类型到枚举的映射,您只需将静态constexpr
成员变量添加到 foo
。但是,对于枚举到类型的映射,我不知道可以使用任何其他方法来代替朋友注入。
Of course, for the type-to-enum mapping you could simply add a static constexpr
member variable to foo
. But I don't know any good alternatives to friend-injection for the enum-to-type mapping.
这篇关于简洁的双向静态1:1值和类型映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!