在 C++ 中对类型进行排序

0.前言

在 C++ 中,我编写了一个 tuple-like 模板,这个模板能容纳任意多且可重复的类型:

template<typename... Ts>
struct TypeList {};// usage:
using List1 = TypeList<int, double, char, double>;
using List2 = TypeList<>;

因为 C++ 的模板系统是图灵完备的,所以我可以判断某个类型是否存在某个列表中:

#include <type_traits>template<typename TpList, typename T>
struct Own;
template<typename T>
struct Own<TypeList<>, T> : std::false_type {};
template<typename Head, typename... Tail, typename T>
struct Own<TypeList<Head, Tail...>, T>: std::conditional<std::is_same<T, Head>::value, std::true_type, Own<TypeList<Tail...>, T>>::type {};static_assert( Own<TypeList<double, void, int>, int>::value, "" );
static_assert( !Own<TypeList<double, void, int>, long long>::value, "" );

类似的,我也可以为这个 TypeList 添加一系列用于增删查改的模板:

// 在一个 TypeList 首部插入一个元素
template<typename TpList, typename T>
struct PushFront;
template<typename TpList, typename T>
using PushFront_t = typename PushFront<TpList, T>::type;template<typename... Ts, typename T>
struct PushFront<TypeList<Ts...>, T> {using type = TypeList<T, Ts...>;
};// 提取第 I 个类型
template<std::size_t Pos, typename... Ts>
struct TypeAt {static_assert( static_cast<bool>( Pos < sizeof...( Ts ) ), "Position overflow" );
};
template<std::size_t Pos, typename... Ts>
using TypeAt_t = typename TypeAt<Pos, Ts...>::type;
// C++26 后可以用 Ts...[Pos] 取代这里的递归遍历template<std::size_t Pos, typename T, typename... Ts>
struct TypeAt<Pos, T, Ts...> : TypeAt<Pos - 1, Ts...> {};
template<typename T, typename... Ts>
struct TypeAt<0, T, Ts...> {using type = T;
};

很显然,因为 TypeList 是一个线性容器,从中查找某个元素时需要从头遍历整个列表。

但可以注意到:当我们使用 std::is_base_of 检查某个类型是否是一个类型的基类时,这个操作的结果可以经由类型协变直接返回,所以我们可以据此编写一个 TypeSet

实际上这里所谓的“直接返回”不太准确;从类型检查角度来说,某个类型是否能协变为基类类型肯定是需要编译器查询确认的,只不过在模板元编程层面上我们看不到这里的遍历查找过程。

template<typename... Ts>
struct TypeSet : TypeList<Ts>... {};
// 这里用 TypeList 包一下,是因为类似 int、double 的基本数据类型不允许被继承template<typename TpSet, typename T>
struct Exist;
template<typename... Ts, typename T>
struct Exist<TypeSet<Ts...>, T> : std::is_base_of<TypeList<T>, TypeSet<Ts...>> {};static_assert( Exist<TypeSet<double, char, float>, char>::value, "" );
static_assert( !Exist<TypeSet<double, char, float>, int>::value, "" );

C++ 不允许在多继承中重复继承多个相同的基类,所以如果 TypeSet 包含了重复元素就会直接触发一次编译硬错误。

不幸的是,这个硬错误无法被运用在 SFINAE 中,以检查多个类型列表是否包含重复元素。

在不涉及模板实例化的场景下使用一个明显包含重复元素的 TypeSet 并不会立即导致编译错误,

只有抵达了 std::is_base_of 之类需要展开继承关系的场景才会触发,

这种错误不能被 SFINAE 忽略,因为这个错误是由模板实例化尝试导致的。

而且因为不能使用 SFINAE 拒绝重复元素模板,所以如果想从 TypeList 构造一个 TypeSet,就需要提前检查前者是否包含重复元素。

一个朴素的查重实现可以由递归遍历操作得到:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename Head, typename... Tail>
struct Duplicated<TypeList<Head, Tail...>>: std::conditional<Belong<Head, TypeList<Tail...>>::value, std::true_type, Duplicated<TypeList<Tail...>>>::type {};

这个实现过于朴素,以至于它的复杂度达到了 O ( n 2 ) O(n^{2}) O(n2)

正常来说,如果想要有一个更优的复杂度实现,我们会首先排序待查找集合中的所有元素,然后线性查找重复的相邻元素,这能做到 O ( n log ⁡ n + n ) O(n\log{n}+n) O(nlogn+n)

那么我们能否对 C++ 的类型进行排序?

题外话:其实这里也可以仅靠一个记录了遍历过的所有类型的 TypeSet 做到线性时间复杂度的查重,这只需要实现一个向集合中无重复添加元素的 Expand 模板。

这种实现的代码可以看这里。

但是直接操纵类型,将它们随心所欲地排序还是很好玩的,所以下面依然会围绕这个需求展开(

1.指针标记

只要我们能给每一个类型赋予一个可比较的类型 ID,那么自然的,整个类型集合就是可以被排序的。

虽然在 C++ 里,只要是重载了比较运算符的类型都可以被比较,但因为我们要对类型进行运算,所以我们要把这个操作限定在编译期。

编译期可用的类型包括算术类型和指针类型,很容易想到的是,我们可以使用一个指向某个实例化模板的指针实现类型 ID:

using ID = typename std::add_pointer<void() noexcept>::type;template<typename T>
struct TypeID {
private:static constexpr void id() noexcept {}public:static constexpr ID value = &id;
};template<ID... Is>
struct IDList {};using IDs = IDList<TypeID<int>::value, TypeID<double>::value>;

很不幸的是,在尝试使用这种方法时,编译器(或者说标准)会抱怨:对指针的比较操作不属于编译期常量表达式。

error: '(TypeID<int>::id < TypeID<double>::id)' is not a constant expression| constexpr bool value = TypeID<int>::value < TypeID<double>::value;|                        ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~

不过判等比较是没问题的。

出现这种错误的主要原因在于:执行比较时我们还处在编译期,代码生成没有完全结束,此时拿到的指针值纯粹是一个不可比较的类型标记,就和类型本身一样。

相同的,对指针做类似 reinterpret_cast 的转换也不属于一个编译期表达式。

指针这条路走不通,那么我们只能将目标转移到整数类型上了。

在下一节前,特别需要指出的是:

尽管 typeid() 产生的 std::type_info 类型提供了一个返回整数的 hash_code() 方法,

std::type_info 对象的生成会(被故意)延迟到运行期,所以 hash_code() 也不属于一个常量表达式。

2.整数标记

在 GCC、Clang 和 MSVC 中,编译器为我们提供了以下两个特殊的扩展宏:

// GCC、Clang:
__PRETTY_FUNCTION__// MSVC:
__FUNCSIG__

这两个宏的作用是以字符数组的形式返回当前函数的名称。

与 C++ 标准的 __func__ 变量不同,这两个宏带有更多与函数参数类型有关的信息,而我们可以借助它附带的类型信息将每一个类型映射为独一无二的整数值。

#include <iostream>template<typename T>
void foo()
{
#if defined( _MSC_VER )std::cout << __FUNCSIG__ << std::endl;
#elsestd::cout << __PRETTY_FUNCTION__ << std::endl;
#endif
}int main()
{foo<int>();foo<double>();foo<std::string>();
}

上述代码的输出为:

// gcc:
void foo() [with T = int]
void foo() [with T = double]
void foo() [with T = std::__cxx11::basic_string<char>]// clang:
void foo() [T = int]
void foo() [T = double]
void foo() [T = std::basic_string<char>]// msvc:
void __cdecl foo<int>(void)
void __cdecl foo<double>(void)
void __cdecl foo<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >>(void)

在 C++20 中,标准库引入了一个名为 std::source_location 的组件,这个组件可以在一定程度上获取当前源码上下文的信息。

特别是 std::source_location::function_name() 方法,标准称这个方法会返回一个由实现而异的、能表示当前函数名称的字符串,这个方法在 GCC、Clang 和 MSVC 上的实现与上述两个宏是相同的。

所以在 GCC、Clang、MSVC,亦或者 C++20 之后的 C++ 环境下,我们可以利用函数名字符串为每个类型计算得到一个独特 ID:

#include <cstdint>
#if defined( __cpp_lib_source_location )
# include <source_location>
#endif#if defined( __cpp_lib_source_location ) || defined( __GNUC__ ) || defined( __clang__ ) || defined( _MSC_VER )
using ID = std::uint64_t;template<typename T>
struct TypeID {
private:static constexpr ID avalanche( ID hash ) noexcept{ // 进行后处理是因为 fnv1a 对尾部字符差异不敏感,而 GCC 和 Clang 的函数字符串差异主要体现在尾部return ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )^ ( ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )>> 33 );}static constexpr ID fnv1a( const char* type_name, ID hash = 14695981039346656037ull ) noexcept{ // 因为函数签名是固定的,变动的只有模板参数名,所以这里也可以简单地累加 type_name 中的每个字符// 虽然累加得到的整数值会比较接近return *type_name == '\0'? avalanche( hash ): fnv1a( type_name + 1, ( hash ^ static_cast<ID>( *type_name ) ) * 1099511628211ull );}template<typename U>static constexpr ID id() noexcept{
# if defined( __cpp_lib_source_location )const auto location = std::source_location::current();return fnv1a( location.function_name() );
# elif defined( _MSC_VER )return fnv1a( __FUNCSIG__ );
# elsereturn fnv1a( __PRETTY_FUNCTION__ );
# endif}public:static constexpr ID value = id<typename std::remove_cv<typename std::remove_reference<T>::type>::type>();
};
#endif

现在我们拥有了将不同类型映射为一个唯一的整数 ID 的方法:

#include <iostream>int main()
{std::cout << TypeID<int>::value << std::endl<< TypeID<double>::value << std::endl<< TypeID<TypeID<int>>::value << std::endl<< TypeID<std::string>::value << std::endl<< TypeID<std::ostream>::value << std::endl;
}

以上代码的输出见此。

3.类型排序

因为类型是可以通过 std::is_same 直接判等的,所以我们可以直接排序一个 TypeList 而不需要单独创建 IDList

排序算法可以随便选择一个,我用的是归并排序:

#include <utility>template<typename TpList, std::size_t Ratio = 2>
struct Split;
template<typename TpList, std::size_t Ratio = 2>
using Split_l = typename Split<TpList, Ratio>::left;
template<typename TpList, std::size_t Ratio = 2>
using Split_r = typename Split<TpList, Ratio>::right;template<std::size_t Ratio>
struct Split<TypeList<>, Ratio> {using left  = TypeList<>;using right = TypeList<>;
};
template<typename T, typename... Ts, std::size_t Ratio>
struct Split<TypeList<T, Ts...>, Ratio> {
private:static_assert( Ratio > 0, "Math error" );static constexpr std::size_t N = 1 + sizeof...( Ts );static constexpr std::size_t H = N / Ratio;template<std::size_t... I>static constexpr TypeList<TypeAt_t<I, T, Ts...>...> make_left( const std::index_sequence<I...>& );template<std::size_t... I>static constexpr TypeList<TypeAt_t<I + H, T, Ts...>...> make_right( const std::index_sequence<I...>& );public:using left  = decltype( make_left( std::make_index_sequence<H> {} ) );using right = decltype( make_right( std::make_index_sequence<N - H> {} ) );
};template<typename TpList>
struct MergeSort;
template<typename TpList>
using MergeSort_t = typename MergeSort<TpList>::type;template<>
struct MergeSort<TypeList<>> {using type = TypeList<>;
};
template<typename T>
struct MergeSort<TypeList<T>> {using type = TypeList<T>;
};
template<typename... Ts>
struct MergeSort<TypeList<Ts...>> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};// 因为是按哈希 ID 排序,所以这里升降序没有意义,也就不提供模板谓词了template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<( TypeID<U>::value < TypeID<V>::value ),PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>>, MergeSort_t<Split_r<TypeList<Ts...>>>>;
};using List       = TypeList<char, unsigned char, double, int, float>;
using SortedList = MergeSort_t<TypeList<char, unsigned char, double, int, float>>;// 一般来说两个列表元素顺序是不同的,但这取决于具体的编译器实现
static_assert( !std::is_same<List, SortedList>::value, "" );

4.类型查重

经过排序后的 TypeList 中,重复的元素(类型)会位于相邻的位置上,所以查重模板 Duplicated 可以简化为以下的线性查找实现:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList>>::value;
};

现在可以用它检查一个 TypeList 是否包含重复元素,并且能否转换为一个 TypeSet 了。

static_assert( !Duplicated<TypeList<int, double, short, char>>::value, "" );
static_assert( Duplicated<TypeList<int, double, int, float>>::value, "" );

5.兼容性处理

由于 TypeID 基于特定的编译器宏、亦或者 C++20 的 std::srouce_location 实现了类型哈希,所以以上代码只适用于使用 GCC、Clang、MSVC 或者 C++20 的环境;在这些环境之外,只能提供一个朴素实现的 fallback。

实际上,如果能通过静态反射拿到一些可以排序的类型元数据的话,这里的实现可以更简单。

所以wo静态反射ne?

完整的类型查重实现代码为:

#include <cstdint>
#if defined( __cpp_lib_source_location )
# include <source_location>
#endif#if defined( __cpp_lib_source_location ) || defined( __GNUC__ ) || defined( __clang__ ) || defined( _MSC_VER )
using ID = std::uint64_t;template<typename T>
struct TypeID {
private:static constexpr ID avalanche( ID hash ) noexcept{return ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )^ ( ( ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull )^ ( ( ( hash ^ ( hash >> 33 ) ) * 0xFF51AFD7ED558CCDull ) >> 33 ) )* 0xC4CEB9FE1A85EC53ull )>> 33 );}static constexpr ID fnv1a( const char* type_name, ID hash = 14695981039346656037ull ) noexcept{ // 因为函数签名是固定的,变动的只有模板参数名,所以这里也可以简单地累加 type_name 中的每个字符// 虽然累加得到的整数值会比较接近return *type_name == '\0'? avalanche( hash ): fnv1a( type_name + 1, ( hash ^ static_cast<ID>( *type_name ) ) * 1099511628211ull );}template<typename U>static constexpr ID id() noexcept{
# if defined( __cpp_lib_source_location )const auto location = std::source_location::current();return fnv1a( location.function_name() );
# elif defined( _MSC_VER )return fnv1a( __FUNCSIG__ );
# elsereturn fnv1a( __PRETTY_FUNCTION__ );
# endif}public:static constexpr ID value = id<typename std::remove_cv<typename std::remove_reference<T>::type>::type>();
};#include <utility>template<typename TpList, std::size_t Ratio = 2>
struct Split;
template<typename TpList, std::size_t Ratio = 2>
using Split_l = typename Split<TpList, Ratio>::left;
template<typename TpList, std::size_t Ratio = 2>
using Split_r = typename Split<TpList, Ratio>::right;template<std::size_t Ratio>
struct Split<TypeList<>, Ratio> {using left  = TypeList<>;using right = TypeList<>;
};
template<typename T, typename... Ts, std::size_t Ratio>
struct Split<TypeList<T, Ts...>, Ratio> {
private:static_assert( Ratio > 0, "Math error" );static constexpr std::size_t N = 1 + sizeof...( Ts );static constexpr std::size_t H = N / Ratio;template<std::size_t... I>static constexpr TypeList<TypeAt_t<I, T, Ts...>...> make_left( const std::index_sequence<I...>& );template<std::size_t... I>static constexpr TypeList<TypeAt_t<I + H, T, Ts...>...> make_right( const std::index_sequence<I...>& );public:using left  = decltype( make_left( std::make_index_sequence<H> {} ) );using right = decltype( make_right( std::make_index_sequence<N - H> {} ) );
};template<typename TpList>
struct MergeSort;
template<typename TpList>
using MergeSort_t = typename MergeSort<TpList>::type;template<>
struct MergeSort<TypeList<>> {using type = TypeList<>;
};
template<typename T>
struct MergeSort<TypeList<T>> {using type = TypeList<T>;
};
template<typename... Ts>
struct MergeSort<TypeList<Ts...>> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<( TypeID<U>::value < TypeID<V>::value ),PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>>, MergeSort_t<Split_r<TypeList<Ts...>>>>;
};template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList>>::value;
};
#else
template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename Head, typename... Tail>
struct Duplicated<TypeList<Head, Tail...>>: std::conditional<Belong<Head, TypeList<Tail...>>::value, std::true_type, Duplicated<TypeList<Tail...>>>::type {};
#endif

6.总结展望

实际上,由于我们已经实现了一个排序算法,所以只要有一个能够表示不同类型之间偏序关系的谓词,就可以将这个排序操作作用在任何概念上。

为此我们需要泛化一下先前的归并排序模板:

template<typename TpList, template<typename, typename> class Cmp>
struct MergeSort;
template<typename TpList, template<typename, typename> class Cmp>
using MergeSort_t = typename MergeSort<TpList, Cmp>::type;template<template<typename, typename> class Cmp>
struct MergeSort<TypeList<>, Cmp> {using type = TypeList<>;
};
template<typename T, template<typename, typename> class Cmp>
struct MergeSort<TypeList<T>, Cmp> {using type = TypeList<T>;
};
template<typename... Ts, template<typename, typename> class Cmp>
struct MergeSort<TypeList<Ts...>, Cmp> {
private:template<typename LeftList, typename RightList>struct Conquer;template<typename LeftList, typename RightList>using Conquer_t = typename Conquer<LeftList, RightList>::type;template<typename... Us>struct Conquer<TypeList<>, TypeList<Us...>> {using type = TypeList<Us...>;};template<typename... Us>struct Conquer<TypeList<Us...>, TypeList<>> {using type = TypeList<Us...>;};template<typename U, typename... Us, typename V, typename... Vs>struct Conquer<TypeList<U, Us...>, TypeList<V, Vs...>>: std::conditional<Cmp<U, V>::value,PushFront<Conquer_t<TypeList<Us...>, TypeList<V, Vs...>>, U>,PushFront<Conquer_t<TypeList<U, Us...>, TypeList<Vs...>>, V>>::type {};public:using type =Conquer_t<MergeSort_t<Split_l<TypeList<Ts...>>, Cmp>, MergeSort_t<Split_r<TypeList<Ts...>>, Cmp>>;
};

然后添加一个谓词模板:

// 按 ID 升序
template<typename A, typename B>
struct IDLess : std::integral_constant<bool, ( TypeID<A>::value < TypeID<B>::value )> {};

最后修改 Duplicated 的实现:

template<typename TpList>
struct Duplicated;
template<>
struct Duplicated<TypeList<>> : std::false_type {};
template<typename TpList>
struct Duplicated {
private:template<typename Types>struct Helper;template<typename T>struct Helper<TypeList<T>> : std::false_type {};template<typename T1, typename T2, typename... Ts>struct Helper<TypeList<T1, T2, Ts...>>: std::conditional<std::is_same<T1, T2>::value, std::true_type, Helper<TypeList<T2, Ts...>>>::type {};public:static constexpr bool value = Helper<MergeSort_t<TpList, IDLess>>::value;
};

我们已经将谓词模板暴露了出来,所以现在我们还能用这个排序算法实现一些更有意思的操作。

在 C++ 里,struct/class 的成员会受到不同的对齐字节要求,导致最终的 struct/class 类型的大小可能比实际成员大小之和略大;这在 std::tuple 中体现的极为明显:

#include <tuple>using tup = std::tuple<bool, void*, char, int>;
static_assert( sizeof( tup ) != ( sizeof( bool ) + sizeof( void* ) + sizeof( char ) + sizeof( int ) ),"" );

如果我们定义一个基于类型大小降序的谓词模板,那么就可以利用排序算法重排列 std::tuple 的类型列表:

降序是因为在 std::tuple 的主流实现中,更靠左的类型参数会位于类型成员的“更高处”。

template<typename A, typename B>
struct SizeGreater : std::integral_constant<bool, ( sizeof( A ) > sizeof( B ) )> {};

为了协调 TypeListstd::tuple,还需要为它们编写一个类型转换模板:

// 不需要为 std::tuple -> TypeList 编写
// 因为这个类型转换可以利用模板特化匹配完成
template<typename TpList>
struct List2Tuple;
template<typename TpList>
using List2Tuple_t = typename List2Tuple<TpList>;template<typename... Ts>
struct List2Tuple<TypeList<Ts...>> {using type = std::tuple<Ts...>;
};

因为重排 std::tuple 会导致不同类型根据它们的实现大小被调换到不同的位置上,导致通过下标访问成员变得比较没什么意义。

这里可以用上之前的查重模板 Duplicated,确保传入和生成的 std::tuple 不存在重复类型,使得可以仅通过类型就查找到对应成员:

template<typename Tuple>
struct ReorderTuple;
template<typename Tuple>
using ReorderTuple_t = typename ReorderTuple<Tuple>::type;template<typename... Ts>
struct ReorderTuple<std::tuple<Ts...>> {static_assert( !Duplicated<TypeList<Ts...>>::value, "Duplicate types are not allowed" );using type = List2Tuple_t<MergeSort_t<TypeList<Ts...>, SizeGreater>>;
};

简单编写一个 std::tuple 就可以看到结果:

using Tup       = std::tuple<bool, void*, char, long long>;
using Reordered = ReorderTuple_t<Tup>;
static_assert( !std::is_same<Tup, Reordered>::value, "" );
static_assert( sizeof( Tup ) > sizeof( Reordered ), "" );

本文使用到的所有代码均可以在此找到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity-Socket通信实例详解

今天我们来讲解socket通信。 首先我们需要知道什么是socket通信&#xff1a; Socket本质上就是一个个进程之间网络通信的基础&#xff0c;每一个Socket由IP端口组成&#xff0c;熟悉计网的同学应该知道IP主要是应用于IP协议而端口主要应用于TCP协议&#xff0c;这也证明了Sock…

使用Go语言对接全球股票数据源API实践指南

使用Go语言对接全球股票数据API实践指南 概述 本文介绍如何通过Go语言对接支持多国股票数据的API服务。我们将基于提供的API文档&#xff0c;实现包括市场行情、K线数据、实时推送等核心功能的对接。 一、准备工作 1. 获取API Key 联系服务提供商获取访问密钥&#xff08;替…

LeetCode 热题 100 17. 电话号码的字母组合

LeetCode 热题 100 | 17. 电话号码的字母组合 大家好&#xff0c;今天我们来解决一道经典的算法题——电话号码的字母组合。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。下面我将详细讲解解…

OpenCV计算机视觉实战(3)——计算机图像处理基础

OpenCV计算机视觉实战&#xff08;3&#xff09;——计算机图像处理基础 0. 前言1. 像素和图像表示1.1 像素 2. 色彩空间2.1 原色2.2 色彩空间2.3 像素和色彩空间 3. 文件类型3.1 图像文件类型3.2 视频文件3.3 图像与视频 4. 计算机图像编程简史5. OpenCV 概述小结系列链接 0. …

Vite 的工作流程

Vite 的工作流程基于其创新的 “预构建 按需加载” 机制&#xff0c;通过利用现代浏览器对原生 ES 模块的支持&#xff0c;显著提升了开发效率和构建速度。以下是其核心工作流程的详细分析&#xff1a; 一、开发环境工作流程 1. 启动开发服务器 冷启动&#xff1a;通过 npm …

线性DP(动态规划)

线性DP的概念&#xff08;视频&#xff09; 学习线性DP之前&#xff0c;请确保已经对递推有所了解。 一、概念 1、动态规划 不要去看网上的各种概念&#xff0c;什么无后效性&#xff0c;什么空间换时间&#xff0c;会越看越晕。从做题的角度去理解就好了&#xff0c;动态规划…

MySQL中sql_mode的设置

■ 57版本原来配置 show variables like %sql_mode%; STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION ■ 修改配置文件 注释掉sql_mode&#xff0c;并重启&#xff0c;查看57版本的默认设置 ONL…

MCAL学习(1)——AutoSAR

1.了解AutoSAR及一些概念 AutoSAR是Automotive Open System Architecture ,汽车开放系统架构。 针对汽车ECU的软件开发架构。已经是汽车电子软件开发的标准。 OS服务&#xff1a;Freertos 整车厂&#xff08;OEM&#xff09;主要负责应用层算法 一级供应商&#xff1a;生产制…

Vue报错:Cannot read properties of null (reading ‘xxx‘)

一、报错问题 Cannot read properties of null (reading style)at patchStyle (runtime-dom.esm-bundler.js:104:22)二、错误排查 这类报错一般是在已经开发好后&#xff0c;后面测试时突然发现的&#xff0c;所以不好排查错误原因。 三、可能原因及解决方案 v-if 导致 在 …

25G 80km双纤BIDI光模块:远距传输的创新标杆

目录 一、产品优势&#xff1a;双纤与BIDI的独特价值 易天光通信25G SFP28 ZR 80KM 易天光通信25G SFP28 BIDI ZR 80KM 二、权威认证与技术突破 三、双纤与BIDI的核心差异解析 四、应用场景&#xff1a;驱动多领域高效互联 总结 在5G、云计算与数字化转型的推动下&#xff0c;光…

2025-05-06 学习记录--Python-注释 + 打印变量 + input输入

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、注释 ⭐️ &#xff08;一&#xff09;、块注释 &#x1f36d; 举例&#xff1a; &#x1f330; # 打印数字 print(2025) …

基于mediapipe深度学习的眨眼检测和计数系统python源码

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 人工智能算法python程序运行环境安装步骤整理_本地ai 运行 python-CSDN博客 3.部分核心程序 &…

怎样通过API 实现python调用Chatgpt,gemini

怎样通过API 实现python调用Chatgpt,gemini 以下为你详细介绍如何设置和调用这些参数,以创建一个类似的 ChatCompletion 请求: 1. 安装依赖库 如果你使用的是 OpenAI 的 API 客户端,需要先安装 openai 库。可以使用以下命令进行安装: pip install openai2. 代码示例 …

Linux 下MySql主从数据库的环境搭建

测试环境&#xff1a;两台服务器&#xff0c;Mysql版本 8.0&#xff0c;linux版本&#xff1a;Ubuntu 20.04.3&#xff1b; 1.在两台服务器上安装MySql&#xff1b; 2.选一台作为主服务器&#xff0c;在主服务器上以root用户进入Mysql&#xff0c;执行以下语句&#xff1a; …

力扣1812题解

记录 2025.5.7 题目&#xff1a; 思路&#xff1a; 从左下角开始&#xff0c;棋盘的行数和列数&#xff08;均从 1 开始计数&#xff09;之和如果为奇数&#xff0c;则为白色格子&#xff0c;如果和为偶数&#xff0c;则为黑色格子。 代码&#xff1a; class Solution {pu…

适合java程序员的Kafka消息中间件实战

创作的初心&#xff1a; 我们在学习kafka时&#xff0c;都是基于大数据的开发而进行的讲解&#xff0c;这篇文章为java程序员为核心&#xff0c;助力大家掌握kafka实现。 什么是kafka: 历史&#xff1a; 诞生与开源&#xff08;2010 - 2011 年&#xff09; 2010 年&#xf…

PDF智能解析与知识挖掘:基于pdfminer.six的全栈实现

前言 在数字化信息爆炸的时代&#xff0c;PDF&#xff08;便携式文档格式&#xff09;作为一种通用的电子文档标准&#xff0c;承载着海量的结构化与非结构化知识。然而&#xff0c;PDF格式的设计初衷是用于展示而非数据提取&#xff0c;这使得从PDF中挖掘有价值的信息成为数据…

Python爬虫+代理IP+Header伪装:高效采集亚马逊数据

1. 引言 在当今大数据时代&#xff0c;电商平台&#xff08;如亚马逊&#xff09;的数据采集对于市场分析、竞品监控和价格追踪至关重要。然而&#xff0c;亚马逊具有严格的反爬虫机制&#xff0c;包括IP封禁、Header检测、验证码挑战等。 为了高效且稳定地采集亚马逊数据&am…

架构思维:探讨架构师的本质使命

文章目录 软件工程1. 软件工程的定义与核心目标2. 软件工程 vs. 软件项目管理3. 软件工程的两大特性4. 软件工程的关键活动与方法论5. 架构师在软件工程中的职责架构师的职责和思维架构师心性修炼三大核心能力架构设计的基本准则 团队共识“设计文档”的统一结构框架阅读他人代…

QT设计权限管理系统

Qt能够简单实现系统的权限设计 首先我们需要一个登陆界面 例如这样 然后一级权限&#xff0c;可以看到所有的内容&#xff0c;不设置菜单栏的隐藏。 然后其他权限&#xff0c;根据登陆者的身份进行菜单栏不同的展示。 菜单栏的隐藏代码如下&#xff1a; ui->actionuser-…