值和类型的简洁双向静态 1:1 映射

Concise bidirectional static 1:1 mapping of values and types

本文关键字:静态 映射 类型 简洁      更新时间:2023-10-16

我将从我想象如何使用我想创建的代码开始。它不必完全像这样,但它是一个很好的例子,说明了我在标题中所说的"简洁"的意思。在我的例子中,它是将类型映射到相关的枚举值。

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

我知道我可以为每个类型-值对手动编写两个模板专用化来执行此操作,但我想知道它是否可以比不使用宏时更乏味。

我已经尝试过的是...

专用
  1. 模板别名,以编写更少的代码来生成专用化。在当前的C++版本(17/20(中显然是不可能的。
  2. 专用继承的模板成员类型。
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 上失败并出现以下错误。

明确的专业化;"foo_base::to_value"已被实例化

"foo_base::to_value":无法在当前范围内专用化模板

我觉得如果没有明确的手动专业化,它可能不可行,但 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.godbolt.org 上运行

它似乎适用于GCC,Clang和MSVC。

我使用的是auto模板参数,因此您可以将不同类型的映射到来自不同枚举的常量,甚至是纯整数。将其限制为仅接受单个特定枚举应该很容易,并且留给读者作为练习。


当然,对于类型到枚举的映射,您可以简单地将static constexpr成员变量添加到foo。但是我不知道枚举到类型映射的朋友注入有什么好的替代方案。

@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; 
}

在 gcc.godbolt.org 上运行

它仍然让我大吃一惊。auto返回类型在这里绝对至关重要。即使将其更改为foo中的T*也会产生编译错误。我还尝试摆脱adl_to_type并使用integral_constant,但似乎将foo_type_to_type声明为用于解析 ADL 的类型中的friend 函数是这里的关键。