Есть ли способ создать карту типов времени компиляции в C ++ 17 для проверки типов? - PullRequest
0 голосов
/ 14 октября 2019

Я немного новичок в метапрограммировании C ++ / SFINAE, и у меня возникли проблемы с разработкой проверки, чтобы увидеть, содержится ли тип, переданный в метод, в предопределенном списке типов. В данном контексте я хотел бы проверить, совпадает ли тип, зарегистрированный в моем варианте, с типом вывода в другой структуре. Каждый элемент, зарегистрированный в моем приложении, сопоставляется с другим элементом (в структуре) с помощью тега (некоторый номер). Я хотел бы создать карту типов, которую можно использовать во время компиляции, чтобы поднять утверждение, если тип, который должен быть зарегистрирован, не соответствует типу элемента в моей структуре протокола проводного соединения.

Итак, что-то вроде:

// register elements in type map:
type_map::register(ID_1, decltype(wire_type_item_1)); 
type_map::register(ID_2, decltype(wire_type_item_2));
... etc.

// and when types are registered
template<typename T>
void add_item(const uint32_t id, const T item)
{
   // add static_assert here
   // look up type based on ID, and compare to type passed in
   // when add_item is called
   static_assert(std::is_same<type_map::find(id), decltype(T), 
                 "Attempted to register type that does not match wire type"); 

   ...
} 

Буду признателен за любые указания о том, с чего начать / как это сделать - спасибо!

Ответы [ 4 ]

0 голосов
/ 14 октября 2019

Следуя (и недоразумению?) Предложению в комментарии от Yakk (спасибо) в моем первом ответе, я написал действительно другой (более простой и элегантный, IMHO) способ реализации карты времени компиляции.

Прежде всего, ct_pair. Теперь сложнее с using типом type (для получения параметра типа шаблона T) и функцией static, которая, учитывая тот же индекс структуры (заключенный в std::integral_constant), имеет разныетипы из различных реализаций ct_pair)

template <std::size_t I, typename T>
struct ct_pair
 { 
   using type = T;

   static ct_pair get_pair (std::integral_constant<std::size_t, I>)
    { return {}; }
 };

Теперь std::tuple, std::tuple_cat() и вспомогательный класс get_tuple больше не нужны и ct_map просто становятся

template <typename ... Ps>
struct ct_map : public Ps...
 {
   using Ps::get_pair...;

   template <std::size_t I>
   using find_type
      = typename decltype(get_pair(
            std::integral_constant<std::size_t, I>{}))::type;
 };

Теперь полный пример (к сожалению, C ++ 17 и не раньше, когда другой мой ответ должен быть совместим с C ++ 11 / C ++ 14) становится

#include <tuple>
#include <iostream>
#include <type_traits>

template <std::size_t I, typename T>
struct ct_pair
 { 
   using type = T;

   static ct_pair get_pair (std::integral_constant<std::size_t, I>)
    { return {}; }
 };

template <typename ... Ps>
struct ct_map : public Ps...
 {
   using Ps::get_pair...;

   template <std::size_t I>
   using find_type
      = typename decltype(get_pair(
            std::integral_constant<std::size_t, I>{}))::type;
 };

using type_map = ct_map<ct_pair<2u, char>,
                        ct_pair<3u, int>,
                        ct_pair<5u, long>,
                        ct_pair<7u, long long>>;

int main()
 {
   static_assert( std::is_same_v<type_map::find_type<5u>, long> );
 }
0 голосов
/ 14 октября 2019

Не уверен, что именно вам нужно ... в любом случае ...

Конечно, вы не можете использовать значение const std::uint32_t id для получения (find(id)) типа(в качестве значения функции?), которую вы можете использовать в static_assert()

template <typename T>
void add_item (std::uint32_t const id, T const item)
// ................................^^  the value id is unusable in a static_assert()

Если вам известно значение id compile time (иначе ваш вопрос не имеет смысла) вы можете передать его (я предлагаю как std::size_t) в качестве значения шаблона в std::integral_constant

template <std::size_t ID, uintypename T>
void add_item (std::integral_constant<std::size_t, ID>, T const item)

или лучше, я полагаю, непосредственно в качестве параметра шаблона, который выдолжен явно вызывать add_item().

В любом случае ... для карты типов, которую я предлагаю, прежде всего, ct_pair (пара времени компиляции) между std::size_t и типом

template <std::size_t, typename>
struct ct_pair
 { };

Имея также несколько вспомогательных структур следующим образом

template <std::size_t, std::size_t, typename>
struct get_tuple
 { using type = std::tuple<>; };

template <std::size_t I, typename T>
struct get_tuple<I, I, T>
 { using type = std::tuple<T>; };

вы можете создать ct_map (карта времени компиляции), используя специализацию шаблона, мощность std::tuple_cat() вместе с std::get_val() и decltype() следующим образом

template <typename ...>
struct ct_map;

template <std::size_t ... Is, typename ... Ts>
struct ct_map<ct_pair<Is, Ts>...>
 {
   template <std::size_t I>
   static constexpr auto find_type_func ()
    -> decltype( std::get<0>( std::tuple_cat(
             std::declval<typename get_tuple<I, Is, Ts>::type>()...)) );

   template <std::size_t I>
   using find_type
      = std::remove_reference_t<decltype( find_type_func<I>() )>;
 };

Чтобы зарегистрировать элементы на карте, вы должны определить using

using type_map = ct_map<ct_pair<2u, char>,
                        ct_pair<3u, int>,
                        ct_pair<5u, long>,
                        ct_pair<7u, long long>>;

и проверка static_assert() станет чем-то

static_assert( std::is_same_v<type_map::find_type<5u>, long> );

Ниже приведен пример полной компиляции C ++ 17

#include <tuple>
#include <iostream>
#include <type_traits>

template <std::size_t, typename>
struct ct_pair
 { };

template <std::size_t, std::size_t, typename>
struct get_tuple
 { using type = std::tuple<>; };

template <std::size_t I, typename T>
struct get_tuple<I, I, T>
 { using type = std::tuple<T>; };

template <typename ...>
struct ct_map;

template <std::size_t ... Is, typename ... Ts>
struct ct_map<ct_pair<Is, Ts>...>
 {
   template <std::size_t I>
   static constexpr auto find_type_func ()
    -> decltype( std::get<0>( std::tuple_cat(
             std::declval<typename get_tuple<I, Is, Ts>::type>()...)) );

   template <std::size_t I>
   using find_type
      = std::remove_reference_t<decltype( find_type_func<I>() )>;
 };

using type_map = ct_map<ct_pair<2u, char>,
                        ct_pair<3u, int>,
                        ct_pair<5u, long>,
                        ct_pair<7u, long long>>;

int main()
 {
   static_assert( std::is_same_v<type_map::find_type<5u>, long> );
 }
0 голосов
/ 14 октября 2019
template<auto i>
using constant = std::integral_constant<decltype(i), i>;

template<class T>
struct tag_t {
  using type=T;
  // comparison with other tags!
  constexpr auto operator==( tag_t<T> ) const { return std::true_type{}; }
  constexpr auto operator!=( tag_t<T> ) const { return std::false_type{}; }
  template<class U>
  constexpr auto operator==( tag_t<U> ) const { return std::false_type{}; }
  template<class U>
  constexpr auto operator!=( tag_t<U> ) const { return std::true_type{}; }
};
template<class T>
constexpr tag_t<T> tag = {};

Теперь мы можем работать с целыми числами и типами в качестве значений.

type_map::register(ID_1, decltype(wire_type_item_1)); 
type_map::register(ID_2, decltype(wire_type_item_2));
... etc.

это становится

auto wire_type_map( constant<ID_1> ) { return tag<wire_type_item_1>; }
auto wire_type_map( constant<ID_2> ) { return tag<wire_type_item_1>; }

эта регистрация может быть выполнена распределенным способом, еслион виден (из заголовочных файлов) в точке использования.

// and when types are registered
template<typename T, uint32_t id>
void add_item(constant<id> id_tag, const T item)
{
   static_assert(wire_type_map( id_tag ) == tag<T>, 
             "Attempted to register type that does not match wire type"); 

   ...
} 

для этого требуется, чтобы целочисленное значение добавленного элемента было постоянной времени компиляции в этой точке.

Часто, если вычтение целого числа и данных с провода, это не практично;нет способа проверить во время компиляции , что целое число, которое вы читаете, соответствует типу. Но есть возможность проверить во время компиляции , что целое число, которое вы предоставляете, соответствует типу.

OTOH, может быть проще полностью удалить целочисленный аргумент и сопоставить тип свместо этого целое число.

0 голосов
/ 14 октября 2019

Вот один из подходов для создания карты времени компиляции между классами.

#include <type_traits>

struct foo {};
struct bar {};
struct ID_1 {};
struct ID_2 {};
struct ID_3 {};

template <class T>
struct type_map {};

template <>
struct type_map<ID_1> { using type = foo; }; // map ID_1 -> foo
template <>
struct type_map<ID_2> { using type = bar; }; // map ID_2 -> bar

int main() {
    static_assert(std::is_same_v<type_map<ID_1>::type, foo>);
    static_assert(std::is_same_v<type_map<ID_2>::type, bar>);
    static_assert(std::is_same_v<type_map<ID_3>::type, int>); // fails, ID_3 isn't in map
    static_assert(std::is_same_v<type_map<ID_1>::type, bar>); // fails, ID_1 is mapped to foo, not bar
}

Однако в вашем примере кода есть следующая строка: static_assert(std::is_same<type_map::find(id), decltype(T), "Attempted to register type that does not match wire type"); Проблема в том, что idпеременная времени выполненияВы не можете использовать static_assert во время выполнения.

Если вы хотите искать типы на этой карте во время выполнения, это немного сложнее. Я бы предложил использовать библиотеку метапрограммирования, такую ​​как hana, поскольку она позволяет выполнять такие вещи, как цикл по всем элементам структуры данных времени компиляции во время выполнения.

...