【问题标题】:Concise bidirectional static 1:1 mapping of values and types值和类型的简明双向静态 1:1 映射
【发布时间】:2020-10-08 10:37:41
【问题描述】:

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

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 允许许多令人惊讶的基于模板的 hack,所以在我放弃这个想法之前想与更有经验的人确认一下。

【问题讨论】:

  • foo_type 是什么?
  • 就我而言,这是一个范围枚举。我在其中一个编辑中添加了这个细节。
  • to_value 很简单:只需让它在bar 内部查看(您可以在foo 包装器中添加任意符号)。
  • 也许朋友功能会有一些用处,它们允许creepy stuff
  • 除了bar 之外,还有更多的派生类型。

标签: c++ templates c++17 template-meta-programming c++20


【解决方案1】:

正如@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!
}

Run on gcc.godbolt.org

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

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


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

【讨论】:

  • 这不是对核心语言问题#2118的利用吗?
  • @Oliv 是的,这就是 CWG 2118 的内容。到目前为止,该标准似乎说它格式正确。 CWG 说they don't like 这个功能,但他们可以说任何他们想要的。 :P 我怀疑他们会找到一种方法(或敢于)在不破坏一些行为良好的程序的情况下将其从语言中删除。
  • 对于有状态的模板元编程,与unconstexpr(仅适用于单个特定版本的 GCC)之类的东西相比,这有点温和,因此更可靠。
【解决方案2】:

@HolyBlackCat 的回答太棒了。 Type-to-enum 可以通过比 ADL 黑客更简单的方式实现,因此我尝试将 enum-to-type 位提炼到最低限度:

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

Run on gcc.godbolt.org

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

【讨论】:

  • 如果用户执行namespace A {int foo_type_to_type;} using namespace A; 之类的操作,则需要额外的 ADL 黑客来防止 ADL 中断。不太可能发生的情况,但如果它坏了,我不想调试这个魔法。 :)
猜你喜欢
  • 1970-01-01
  • 2020-04-10
  • 1970-01-01
  • 2019-12-05
  • 2022-01-13
  • 2013-07-28
  • 2010-11-30
  • 2021-12-14
  • 1970-01-01
相关资源
最近更新 更多