Краткое двунаправленное отображение c 1: 1 значений и типов - PullRequest
6 голосов
/ 18 июня 2020

Я собираюсь начать с того, как я представляю себе использование кода, который хочу создать. Это не обязательно должно быть именно так, но это хороший пример того, что я имею в виду под «кратким» в названии. В моем случае это сопоставление типа со связанным значением перечисления.

struct bar : foo<bar, foo_type::bar> { /* ... */ };
//               \_/  \___________/
//                ^ Type         ^ Value

В идеале это должно происходить автоматически c регистрацию двунаправленного сопоставления между первым параметром шаблона foo, type, а во-вторых, значение, только с синтаксисом наследования и правильными параметрами шаблона, чтобы позже я мог делать то, что в примере ниже.

foo_type value = to_value<bar>; // Should be foo_type::bar
using type = to_type<foo_type::bar>; // Should be bar

Я знаю, что могу вручную написать две специализации шаблона для каждого типа -value пара, чтобы сделать это, но мне интересно, может ли это быть менее утомительным, чем это без использования макросов.

Я уже пробовал ...

  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

Но это не удается с ошибками ниже на MSV C.

явная специализация; 'foo_base :: to_value' уже создан

'foo_base :: to_value': не может специализировать шаблон в текущей области

Я чувствую, что это может быть невозможно без явных ручных специализаций , но C ++ 17 допускает множество неожиданных хаков на основе шаблонов, поэтому хочу подтвердить это у более опытных людей, прежде чем я откажусь от этой идеи.

Ответы [ 2 ]

4 голосов
/ 18 июня 2020

@ HolyBlackCat отвечает: fantasti c. Преобразование типа в перечисление может быть достигнуто более простыми способами, чем хакерство 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 g cc .godbolt.org

Это все еще поражает меня. Тип возврата auto здесь абсолютно необходим. Даже изменение его на T* в foo приведет к ошибке компиляции. Я также пытался избавиться от adl_to_type и использовать вместо него integral_constant, но похоже, что объявление foo_type_to_type как дружественной функции внутри типа, используемого для разрешения ADL, является ключевым здесь.

4 голосов
/ 18 июня 2020

Как сказал @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!
}

Запуск на g cc .godbolt.org

Кажется, он работает как с G CC, Clang, так и с MSV C.

Я использую параметр шаблона auto, поэтому вы можете сопоставить разные типы к константам из разных перечислений или даже к простым целым числам. Ограничить это, чтобы он принимал только одно перечисление c, должно быть несложным и оставлено в качестве упражнения для читателя.


Конечно, для сопоставления типов и перечислений вы можете просто добавить переменную-член static constexpr в foo. Но я не знаю хороших альтернатив дружественному внедрению для сопоставления перечисления с типом.

...