我将以我想像的方式使用我想创建的代码开始。它不必完全像这样,但这是标题中“简洁”的一个很好的例子。就我而言,它是一种类型到相关枚举值的映射。

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
我知道我可以为每个“类型-值”对手动编写两个模板专长来执行此操作,但是我想知道如果不使用宏,它是否可以比这少那么乏味。
我已经尝试过的是...
  • 特化模板别名,以减少编写特化代码的数量。在当前的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上显示以下错误。

    我觉得没有明确的手动专业知识可能无法实现,但是C++ 17允许很多令人惊讶的基于模板的黑客攻击,因此在我放弃这个主意之前,请与更多有经验的人确认。

    最佳答案

    正如@yeputons所说, friend 注入(inject)可以在这里提供帮助。这是一个令人毛骨悚然的功能,我不能说我完全理解它是如何工作的,但是就可以了。

    #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。但是对于枚举到类型的映射,我不知道有什么替代 friend 注入(inject)的好方法。

    关于c++ - 简洁双向静态1 :1 mapping of values and types,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/62448834/

    10-09 17:15