验证(使用 static_assert)元组类型是否遵循某种顺序(状态编译时检查)

Verify (with static_assert) that tuple types follow some order (stateful compile-time check)

对于一些比较老的软件中的序列化应用,我有这样的类型:

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,代表某种数据库table,向量是某种存储类型(细节不重要)。

为了使这个 "kind of" 类型安全,并让所有东西在未来协同工作,我需要确保向元组添加另一种类型的用户遵循序列号。所以另一个元素应该是这个:

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

See my solution that doesn't work online.

我做错了什么?

您的代码有几处错误。第一个在这一行中,模板参数列表扩展中的错字:

__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 和可变参数模板之前创建的。

Live example

来不及玩了?

抱歉,但是...您为什么不简单地使用模板推导?

类似于

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

观察模板模板参数的使用允许将其与 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::tuplestd::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)>>
 { };

以下是使用 boost::tuple

的完整编译 C++14 示例
#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, "!" );
 }