Убедитесь (с помощью static_assert), что типы кортежей следуют некоторому порядку (проверка состояния во время компиляции) - PullRequest
2 голосов
/ 16 октября 2019

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

using T = boost::tuple<
    std::pair<std::integral_constant<uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<uint32_t, 1>, std::vector<double>>
    >;
T events;

Эти постоянные числа равны static constexpr и представляют некоторую таблицу базы данных, а вектор имеет некоторое хранилищеtype (подробности не важны).

Чтобы сделать этот тип типа безопасным и заставить все работать вместе на будущее, я должен убедиться, что пользователь, который добавляет другой тип в кортеж, следует серийному номеру,Таким образом, еще один элемент должен быть таким :

using T = boost::tuple<
    std::pair<std::integral_constant<uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<uint32_t, 2>, std::vector<float>>
    >;
T events;

Это неправильно и не должно компилироваться :

using T = boost::tuple<
    std::pair<std::integral_constant<uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<uint32_t, 1>, std::vector<float>>
    >;
T events;

Мое решение до сих пор не удался, и я был бы признателен за чей-то вклад в это:

template <typename Tuple>
struct tuple_assert;

template <typename... Ts>
struct tuple_assert<boost::tuple<Ts...>> {
  static constexpr void verify() {
    __verify<std::integral_constant<uint32_t, 0>, Ts...>();
  }

  static constexpr void __verify() {}

  template <typename Count, typename Pair>
  static constexpr void __verify() {
        static_assert(std::is_same<typename Pair::first_type, Count>::value, "");
  }

  template <typename Count, typename Pair, typename... Ts2>
  static constexpr void __verify() {
    static_assert(std::is_same<typename Pair::first_type, Count>::value, "");

    __verify<std::integral_constant<uint32_t, Pair::first_type::value + 1>,
             Ts...>();
  }
};

Итак, вы видите, что я создал состояние (Count), и я увеличиваюсчитать на каждой итерации. Но это каким-то образом достигает неправильного состояния, и static_assert() срабатывает, когда я использую его с этим вызовом:

tuple_assert<T>::verify(); // `T` is the type I mentioned at the very beginning

См. Мое решение, которое не работает в Интернете.

Что я делаю не так?

Ответы [ 2 ]

1 голос
/ 16 октября 2019

Слишком поздно играть?

Извините, но ... почему бы вам просто не использовать шаблонное вычитание?

Что-то вроде

template <typename>
struct check_tuple;

template <template <typename...> class Tpl, std::uint32_t ... Is,
          typename ... Ts>
struct check_tuple<Tpl<
   std::pair<std::integral_constant<std::uint32_t, Is>, Ts>...>>
   : public std::is_same<
      std::integer_sequence<std::uint32_t, Is...>,
      std::make_integer_sequence<std::uint32_t, sizeof...(Is)>>
 { };

Заметьте, что использование параметра template-template позволяет использовать его с std::tuple, а также с boost::tuple. [неправильно: см. Следующую редакцию]

Ниже приводится полная компиляция C ++ 14пример

#include <tuple>
#include <vector>

using T1 = std::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 2>, std::vector<float>>
    >;

using T2 = std::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<float>>
    >;

template <typename>
struct check_tuple;

template <template <typename...> class Tpl, std::uint32_t ... Is,
          typename ... Ts>
struct check_tuple<Tpl<
   std::pair<std::integral_constant<std::uint32_t, Is>, Ts>...>>
   : public std::is_same<
      std::integer_sequence<std::uint32_t, Is...>,
      std::make_integer_sequence<std::uint32_t, sizeof...(Is)>>
 { };

int main()
 {
   static_assert( check_tuple<T1>::value == true, "!" );
   static_assert( check_tuple<T2>::value == false, "!" );
 }

- РЕДАКТИРОВАТЬ -

Я ошибся: boost::tuple не определен как std::tuple, получая список переменных типов шаблонов, но с фиксированным числом (10, но должно быть возможно изменить его) параметра шаблона типа, по умолчанию boost::tuples::null_type.

Так, например,

using T1 = boost::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 2>, std::vector<float>>
    >;

имеет три std::pair и семь boost::tuples::null_type.

Другими словами, T1

using T1 = boost::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 2>, std::vector<float>>,
    boost::tuples::null_type,
    boost::tuples::null_type,
    boost::tuples::null_type,
    boost::tuples::null_type,
    boost::tuples::null_type,
    boost::tuples::null_type,
    boost::tuples::null_type
    >;

Это нарушает мое предыдущее решение, потому что

template <template <typename...> class Tpl, std::uint32_t ... Is,
          typename ... Ts>
struct check_tuple<Tpl<
   std::pair<std::integral_constant<std::uint32_t, Is>, Ts>...>>

не перехватывает семьфиналы boost::tuples::null_type.

Лучшее, что я могу себе представить для решения этой проблемы, - это преобразование типов boost::tuple против std::tuple, которое удаляет типы boost::tuples::null_type.

Я имею в виду

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

template <>
struct get_tuple<boost::tuples::null_type>
 { using type = std::tuple<>; };

template <typename ... Ts>
auto convert_tuple (boost::tuple<Ts...>)
   -> decltype( std::tuple_cat(std::declval<typename get_tuple<Ts>::type>()...) );

Теперь check_tuple можно переписать следующим образом

template <typename T,
          typename = decltype(convert_tuple(std::declval<T>()))>
struct check_tuple;

template <typename BT, std::uint32_t ... Is, typename ... Ts>
struct check_tuple<BT, std::tuple<
   std::pair<std::integral_constant<std::uint32_t, Is>, Ts>...>>
   : public std::is_same<
      std::integer_sequence<std::uint32_t, Is...>,
      std::make_integer_sequence<std::uint32_t, sizeof...(Is)>>
 { };

Ниже приведен пример полной компиляции C ++ 14 с использованием boost::tuple

#include <tuple>
#include <vector>
#include "boost/tuple/tuple.hpp"

using T1 = boost::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 2>, std::vector<float>>
    >;

using T2 = boost::tuple<
    std::pair<std::integral_constant<std::uint32_t, 0>, std::vector<int>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<double>>,
    std::pair<std::integral_constant<std::uint32_t, 1>, std::vector<float>>
>;

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

template <>
struct get_tuple<boost::tuples::null_type>
 { using type = std::tuple<>; };

template <typename ... Ts>
auto convert_tuple (boost::tuple<Ts...>)
   -> decltype( std::tuple_cat(std::declval<typename get_tuple<Ts>::type>()...) );


template <typename T,
          typename = decltype(convert_tuple(std::declval<T>()))>
struct check_tuple;

template <typename BT, std::uint32_t ... Is, typename ... Ts>
struct check_tuple<BT, std::tuple<
   std::pair<std::integral_constant<std::uint32_t, Is>, Ts>...>>
   : public std::is_same<
      std::integer_sequence<std::uint32_t, Is...>,
      std::make_integer_sequence<std::uint32_t, sizeof...(Is)>>
 { };

int main()
 {
   static_assert( check_tuple<T1>::value == true, "!" );
   static_assert( check_tuple<T2>::value == false, "!" );
 }
1 голос
/ 16 октября 2019

В вашем коде есть несколько ошибок. Первый в этой строке, опечатка в расширении списка аргументов шаблона:

__verify<std::integral_constant<uint32_t, Pair::first_type::value + 1>,
         Ts...>();

Это должно быть:

__verify<std::integral_constant<uint32_t, Pair::first_type::value + 1>,
         Ts2...>(); 

Но, к сожалению, это не исправит это. boost::tuple имеет какие-то странные определения типов внутри с некоторыми nulltype_t. Изменение его на std::tuple все равно не исправит это, поскольку ваш вызов функции __verify неоднозначен. Итак, вместо этого вот мои решения.

std::tuple

template<unsigned V, class T, class ...Args>
struct verify_types {
    static constexpr bool value = V == T::first_type::value && verify_types<V+1, Args...>::value;
};

template<unsigned V, class T>
struct verify_types<V, T> {
    static constexpr bool value = V == T::first_type::value;
};

template<class T>
struct verify_tuple : std::false_type {};

template<class ...Args>
struct verify_tuple<std::tuple<Args...>> : verify_types<0, Args...>{};

boost::tuple

Это немного более запутанный.

template<unsigned V, class T, class ...Args>
struct verify_types {
    static constexpr bool value = V == T::first_type::value && verify_types<V+1, Args...>::value;
};

template<unsigned V, class ...Args>
struct verify_types<V, boost::tuples::null_type, Args...> {
    static constexpr bool value = true;
};

template<unsigned V, class T>
struct verify_types<V, T> {
    static constexpr bool value = V == T::first_type::value;
};

template<class T>
struct verify_tuple : std::false_type {};

template<class ...Args>
struct verify_tuple<boost::tuple<Args...>> : verify_types<0, Args...>{};

Уведомление об обработке boost::tuples::null_type. Это связано с тем, что boost::tuple был создан до C ++ 11 и шаблонов с вариациями.

Живой пример

...