我正在阅读“现代C++设计”(A. Alexandrescu,2002),并在第三章中练习基本的TMP。由于这本书很过时,我用C++17-esque的方式修改了示例代码。
请随时评论任何事情!
#include <cstddef>
#include <iostream>
#include <utility>
#include <type_traits>
// defining a typelist
template <typename T>
void printType() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template <typename...> struct TypeList;
template <>
struct TypeList<> {};
template <typename Head, typename... Tails>
struct TypeList<Head, Tails...> {
using head = Head;
using tails = TypeList<Tails...>;
};
// length of a typelist
template <typename TList> struct Length;
template <typename... Types>
struct Length<TypeList<Types...>> {
static constexpr std::size_t value = sizeof...(Types);
};
template <typename TList>
inline constexpr std::size_t Length_v = Length<TList>::value;
// indexed access
template <typename TList, std::size_t index> struct TypeAt;
template <typename Head, typename... Tails>
struct TypeAt<TypeList<Head, Tails...>, 0> {
using type = Head;
};
template <typename Head, typename... Tails, std::size_t index>
struct TypeAt<TypeList<Head, Tails...>, index> {
static_assert(index < sizeof...(Tails) + 1, "index out of range");
using type = typename TypeAt<TypeList<Tails...>, index - 1>::type;
};
template <typename TList, std::size_t index>
using TypeAt_t = typename TypeAt<TList, index>::type;
// indexof
template <typename TList, typename T> struct IndexOf;
template <typename T>
struct IndexOf<TypeList<>, T> {
static constexpr std::size_t value = -1;
};
template <typename TList, typename T>
inline constexpr std::size_t IndexOf_v = IndexOf<TList, T>::value;
template <typename... Tails, typename T>
struct IndexOf<TypeList<T, Tails...>, T> {
static constexpr std::size_t value = 0;
};
template <typename Head, typename... Tails, typename T>
struct IndexOf<TypeList<Head, Tails...>, T> {
static constexpr std::size_t value = std::is_same_v<Head, T> ? 0 :
(IndexOf_v<TypeList<Tails...>, T> == -1 ? -1 :
IndexOf_v<TypeList<Tails...>, T> + 1);
};
// appending to typelists
template <typename TList1, typename TList2> struct Append;
template <typename... TList, typename T>
struct Append<TypeList<TList...>, T> {
using result_type = TypeList<TList..., T>;
};
template <typename T, typename... TList>
struct Append<T, TypeList<TList...>> {
using result_type = TypeList<T, TList...>;
};
template <typename... TList1, typename... TList2>
struct Append<TypeList<TList1...>, TypeList<TList2...>> {
using result_type = TypeList<TList1..., TList2...>;
};
template <typename TList1, typename TList2>
using Append_t = typename Append<TList1, TList2>::result_type;
// erasing a type from a typelist
template <typename TList1, typename T> struct Erase;
template <typename TList1, typename T>
using Erase_t = typename Erase<TList1, T>::result_type;
template <typename... Tails, typename T>
struct Erase<TypeList<T, Tails...>, T> {
using result_type = TypeList<Tails...>;
};
template <typename Head, typename... Tails, typename T>
struct Erase<TypeList<Head, Tails...>, T> {
using result_type = Append_t<Head, Erase_t<TypeList<Tails...>, T>>;
};
template <typename TList1, typename T> struct EraseAll;
template <typename TList1, typename T>
using EraseAll_t = typename EraseAll<TList1, T>::result_type;
template <typename T>
struct EraseAll<TypeList<>, T> {
using result_type = TypeList<>;
};
template <typename... Tails, typename T>
struct EraseAll<TypeList<T, Tails...>, T> {
using result_type = EraseAll_t<TypeList<Tails...>, T>;
};
template <typename Head, typename... Tails, typename T>
struct EraseAll<TypeList<Head, Tails...>, T> {
using result_type = Append_t<Head, EraseAll_t<TypeList<Tails...>, T>>;
};
// erasing duplicates
template <typename TList> struct NoDuplicates;
template <typename TList>
using NoDuplicates_t = typename NoDuplicates<TList>::result_type;
template <>
struct NoDuplicates<TypeList<>> {
using result_type = TypeList<>;
};
template <typename Head, typename... Tails>
struct NoDuplicates<TypeList<Head, Tails...>> {
using result_type = Append_t<Head, EraseAll_t<NoDuplicates_t<TypeList<Tails...>>, Head>>;
};
// replacing an element
template <typename TList, typename Old, typename New> struct Replace;
template <typename TList, typename Old, typename New>
using Replace_t = typename Replace<TList, Old, New>::result_type;
template <typename Old, typename New>
struct Replace<TypeList<>, Old, New> {
using result_type = TypeList<>;
};
template <typename... Tails, typename Old, typename New>
struct Replace<TypeList<Old, Tails...>, Old, New> {
using result_type = TypeList<New, Tails...>;
};
template <typename Head, typename... Tails, typename Old, typename New>
struct Replace<TypeList<Head, Tails...>, Old, New> {
using result_type = Append_t<Head, Replace_t<TypeList<Tails...>, Old, New>>;
};
template <typename TList, typename Old, typename New> struct ReplaceAll;
template <typename TList, typename Old, typename New>
using ReplaceAll_t = typename ReplaceAll<TList, Old, New>::result_type;
template <typename Old, typename New>
struct ReplaceAll<TypeList<>, Old, New> {
using result_type = TypeList<>;
};
template <typename... Tails, typename Old, typename New>
struct ReplaceAll<TypeList<Old, Tails...>, Old, New> {
using result_type = ReplaceAll_t<TypeList<New, Tails...>, Old, New>;
};
template <typename Head, typename... Tails, typename Old, typename New>
struct ReplaceAll<TypeList<Head, Tails...>, Old, New> {
using result_type = Append_t<Head, ReplaceAll_t<TypeList<Tails...>, Old, New>>;
};
// partially ordering typelists
template <typename TList, typename Base> struct MostDerived;
template <typename TList, typename Base>
using MostDerived_t = typename MostDerived<TList, Base>::result_type;
template <typename Base>
struct MostDerived<TypeList<>, Base> {
using result_type = Base;
};
template <typename Head, typename... Tails, typename T>
struct MostDerived<TypeList<Head, Tails...>, T> {
private:
using cand = MostDerived_t<TypeList<Tails...>, T>;
public:
using result_type = std::conditional_t<std::is_base_of_v<cand, Head>, Head, cand>;
};
template <typename TList> struct Derived2Front;
template <typename TList>
using Derived2Front_t = typename Derived2Front<TList>::result_type;
template <>
struct Derived2Front<TypeList<>> {
using result_type = TypeList<>;
};
template <typename Head, typename... Tails>
struct Derived2Front<TypeList<Head, Tails...>> {
private:
using TheMostDerived = MostDerived_t<TypeList<Tails...>, Head>;
public:
using result_type = Append_t<TheMostDerived, Replace_t<TypeList<Tails...>, TheMostDerived, Head>>;
};
class A {
};
class B : public A {
};
class C : public B {
};
int main() {
using numberList = TypeList<int, double, float, uint8_t>;
printType<numberList>();
std::cout << Length_v<numberList> << '\n';
printType<TypeAt_t<numberList, 2>>();
std::cout << IndexOf_v<numberList, float> << '\n';
using charList = TypeList<char, wchar_t, uint8_t>;
using AppendedList = Append_t<numberList, charList>;
printType<AppendedList>();
printType<Erase_t<AppendedList, uint8_t>>();
printType<EraseAll_t<AppendedList, uint8_t>>();
printType<NoDuplicates_t<AppendedList>>();
printType<Replace_t<AppendedList, uint8_t, size_t>>();
printType<ReplaceAll_t<AppendedList, uint8_t, size_t>>();
using ChainList = TypeList<A, B, C>;
printType<MostDerived_t<ChainList, A>>();
printType<Derived2Front_t<ChainList>>();
}测试结果
void printType() [with T = TypeList<int, double, float, unsigned char>]
4
void printType() [with T = float]
2
void printType() [with T = TypeList<int, double, float, unsigned char, char, wchar_t, unsigned char>]
void printType() [with T = TypeList<int, double, float, char, wchar_t, unsigned char>]
void printType() [with T = TypeList<int, double, float, char, wchar_t>]
void printType() [with T = TypeList<int, double, float, unsigned char, char, wchar_t>]
void printType() [with T = TypeList<int, double, float, long unsigned int, char, wchar_t, unsigned char>]
void printType() [with T = TypeList<int, double, float, long unsigned int, char, wchar_t, long unsigned int>]
void printType() [with T = C]
void printType() [with T = TypeList<C, B, A>]发布于 2021-10-24 09:11:53
总的来说,您的代码看起来非常好。
typename...参数包也匹配零元素,因此没有必要进行以下重载:
template <>
struct TypeList<> {};std::conditional来避免重复代码您可以使用std::conditional根据某些条件选择类型。这允许您合并一些重载,例如,不再需要声明TypeAt<..., 0>:
template <typename Head, typename... Tails, std::size_t index>
struct TypeAt<TypeList<Head, Tails...>, index> {
static_assert(index < sizeof...(Tails) + 1, "index out of range");
using type = typename std::conditional<index == 0, Head, TypeAt<TypeList<Tails...>, index - 1>>::type;
};空打字员缺少的
当您编写各种模板时,也要确保测试参数包为空的角落。您的代码很好地处理空打字员!考虑添加以下测试用例:
using EmptyList = TypeList<>;
printType<EmptyList>();
std::cout << Length_v<EmptyList> << '\n';还测试修改空打字员是否按预期工作,以及从非空打字员中删除所有类型是否会导致空打字员。
发布于 2021-10-24 10:12:03
main()函数中的一个次要点:您忽略了定义uint8_t。我想这应该是来自std::uint8_t的<cstdint>,它从另一个库头中泄漏到全局命名空间--但是依赖它是不可移植的!
发布于 2021-10-25 16:54:24
template <typename Head, typename... Tails, std::size_t index>
struct TypeAt<TypeList<Head, Tails...>, index> {
static_assert(index < sizeof...(Tails) + 1, "index out of range");
using type = typename TypeAt<TypeList<Tails...>, index - 1>::type;
};static_assert是一个很晚的失败。您希望在这里早期失败,因此其他代码可以检测到此故障。
template <typename Head, typename... Tails>
struct TypeList<Head, Tails...> {
using head = Head;
using tails = TypeList<Tails...>;
};我不确定正面/反面是否是打字员的基本属性。
我会为打字员写一个函数,返回头尾。
template<class T>
struct tails {};
template<template<class...>class Z, class T0, class...Ts>
struct tails<Z<T0, Ts...>> {
using type=Z<Ts...>;
};
template<class T>
using tails_t = typename tails<T>::type;
template<class T>
struct head {};
template<template<class...>class Z, class T0, class...Ts>
struct head<Z<T0, Ts...>> {
using type=head;
};
template<class T>
using head_t = typename head<T>::type;这现在适用于任何事情(不仅仅是一个TypeList)。
使用时的语法也需要更少的typename垃圾邮件。
..。
接下来要意识到的是,您正在进行函数式编程。有比你所用的更好的模式。
尝试fmap/join/bind:
template<template<class...>class F, class T>
struct fmap {};
template<template<class...>class F, class T>
using fmap_t = typename fmap<F,T>::type;
template<template<class...>class F, template<class...>class Z, class...Ts>
struct fmap<F, Z<Ts...>> {
using type=Z<F<Ts...>>;
};
template<class L>
struct join;
template<class L>
using join_t = typename join<L>::type;
template<template<class...>class Z, class... T0s, class... T1s, class..Zs>
struct join<Z< Z<T0s...>, Z<T1s...>, Zs... > >:join< Z<Z<T0s...>, Zs... > {};
template<template<class...>class Z, class... T0s, class... T1s, class..Zs>
struct join<Z< Z<T0s...> > > {
using type=Z<T0s...>;
};
template<template<class...>class F, class T>
using bind_t = join_t< fmap_t< F, T > >;现在,我们可以使用上面的原语来表示其余的大部分操作。
template<class Ts, class T>
using append_t = join_t< TypeList< Ts, TypeList<T> > >;擦除只是bind( x->[x] except y->[], Ts )。替换只是bind( x->[x] except y->[z], Ts )。
现在,您可能会发现编写这些文件有点烦人。将元编程应用于救援!
在C++17和以后的版本中,您可以使用。您还可以编写lambda的重载。
template<class Match, class Replace>
constexpr auto ReplaceAll = []( auto Types ) {
return bind(
overload( [](auto x){return x;}, [](tag_t<Match>){ return tag<Replace>; } ),
Types
);
};将TMP转换为这种类型的元编程和返回可能比编写干净的TMP更容易。
https://codereview.stackexchange.com/questions/269320
复制相似问题