一、外围框架
VarTypeDict的外围框架代码示例:
template <typename...TParameters> struct VarTypeDict { template <typename...TTypes> struct Values { public: template <typename TTag, typename TVal> auto Set(TVal&& val) && ; template <typename TTag> const auto& Get() const; }; public: static auto Create() { using namespace NSVarTypeDict; using type = typename Create_<sizeof...(TParameters), Values>::type; return type(); ) };
Create 函数是一个静态成员函数,用于实例化内部使用的类型,并返回该类型的实例。
在函数中,通过 Create_ 模板类来实例化 Values 模板类,然后返回这个实例。这里的 Create_ 模板类负责将模板参数包 TParameters... 和 TTypes... 转发给 Values 模板类。Create_ 模板类根据参数来生成相应的类型。然后,通过 typename Create_<sizeof...(TParameters), Values>::type 来获取最终实例化的类型。最后,通过 return type(); 来创建并返回这个实例。
Create_ 类似于一个元编程辅助类,在实际应用中可能会根据 TParameters 的个数生成相应的类型实例,并将这个类型保存到 type 中。
之前文章的代码:
std::cerr << fun(FParams::Create() .Set<B>(2.4f) .Set<A>(1.3f) .Set<Weight>(0.1f));
根据原文赘述总结:
链式调用的设计模式在某些情况下非常有用,特别是在配置对象、构建器模式或者 Fluent 接口设计中。通过链式调用,可以使代码更加清晰和易读,同时也能够提供更直观的代码结构和调用方式。这种方式可以使得代码更具有表达性,可以一目了然地看出对象的构建过程,提高了代码的可维护性和可读性。
同时,在模板元编程中,链式调用也可以用来对模板参数化的代码进行更加灵活和直观的使用,可以方便地对多个模板参数进行操作和设置。
二、Create函数的实现
原文如下:
Create函数是整个模块首个对外的接口,但这个接口实现时有一个问题。
考虑如下代码:
VarTypeDict<A, B>::Create().Set<A>(true).Set<B>(2.4f);
问题就是Create返回的是Value<TTypes...>的实例。这个实例最终需要包含容器中每个值所对应的具体数据类型。对于这段代码。理想的情况是,Create函数执行完成时,返回的是Value<bool, float>类型的实例。后续Set会依据这个信息来设置数据,但程序是从前向后执行的。在执行Create时,系统无法知道要设置的数据类型(bool, float类型), 它该怎么为TTypes赋值呢?
作者思路:
解决这个问题的一种方式是使用辅助函数或者辅助模板参数来捕获类型信息,这样就可以在Create中显式指定TTypes的类型。在C++中,可以使用辅助函数类型萃取(type_traits)、变参模板参数推导等技术来实现这一点。
举例来说,可以设计一个辅助函数CreateHelper,其参数类型根据Set方法的参数类型进行推导,然后在CreateHelper函数中使用这些类型信息来显式指定TTypes的类型。这样,在调用Create时就可以捕获到需要的类型信息了。
代码示例:
// 辅助类型萃取,用于捕获Set方法的参数类型 template <typename> struct TypeExtractor; template <typename T> struct TypeExtractor { using Type = T; }; // Create辅助函数,捕获Set方法的参数类型并显式指定TTypes的类型 template<typename... TTypes> auto CreateHelper() { return Values<typename TypeExtractor<TTypes>::Type...>(); } // VarTypeDict类模板 template <typename... TTypes> struct VarTypeDict { // Create方法,调用CreateHelper来捕获类型信息 static auto Create() { return CreateHelper<TTypes...>(); } }; int main() { // 调用Create方法并设置值 VarTypeDict<bool, float>::Create().Set<bool>(true).Set<float>(2.4f); return 0; }
书中作者思路:
引入一个“占位类型”:
struct Nullparameter
在Create函数调用之初,用这个占位符类型填充TTypes,在之后的Set中,再来修改这个类型为实际的类型。还是以之前的代码为例(其中每一步调用后都给出了相应的返回类型):
VartypeDict<A, B> ::Create() // Values<Nullparameter, NullParameter> .Set<A>(true) // Values<bool, NullParameter> .Set<B>(2.4f); // Valuues<bool, float>
Create函数如下:
namespace NSVarTypeDict { template <size_t N, template<typename...> class TCont, typename...T> struct Create_ { using type = typename Create_<N - 1, TCont, NullParameter, T...>::type; }; template <template<typename...> class TCont, typename...T> struct Create_<0, TCont, T...> { using type = TCont<T...>; }; } template <typename...TParameters> struct VarTypeDict { // ... static auto Create() { using namespace NSVarTypeDict; using type = typename Create<sizeof...(TParameters), Values>::type; return type(); } };
代码讲解:
以下内容结合原文看!!!
这段代码是一个 C++ 的模板元编程实现,它通过模板递归的方式创建一个通用的变长参数模板类 VarTypeDict。
首先定义了一个命名空间 NSVarTypeDict,用于存放模板元编程相关的代码。
在命名空间 NSVarTypeDict 中,定义了一个模板结构体 Create_。这个结构体接受一个类型模板 TCont 和一系列类型参数 T,它有两个模板参数:size_t N 代表参数包的大小,template<typename...> class TCont 代表模板容器类型,typename...T 则代表参数包中的类型。Create_ 结构体有两个模板特化版本:
1. 第一个特化版本定义了一个递归结构,它根据 N 的值递减,将参数包中的每个参数添加到 TCont 容器中。
2. 第二个特化版本定义了递归结束的条件,当 N 的值为 0 时,创建了最终的 TCont<T...> 类型,并将其赋值给 type。
接着定义了一个模板类 VarTypeDict,它接受一系列模板参数 TParameters。在这个类中,定义了一个静态成员函数 Create()。在这个函数中,首先使用 using namespace NSVarTypeDict; 将 NSVarTypeDict 命名空间中的内容引入到当前作用域中。然后使用模板别名 using type = typename Create<sizeof...(TParameters), Values>::type; 来创建一个类型,该类型是基于参数包 TParameters 的容器类型。最后,使用 return type(); 来创建一个匿名对象并返回。
总的来说,这段代码实现了一个通用的变长参数模板类 VarTypeDict,它能够根据传入的参数类型动态生成对应的容器类型。模板元编程的递归思想和特化版本的定义,使得这个过程能够根据参数包的大小动态展开,实现了通用性和灵活性。
三、Values的主体框架
代码示例:
template <typename...TParameters> struct VarTypeDict { template <typename...TTypes> struct Values { Values() = default; Values(std::shared_ptr<void>(&&input)[sizeof...(TTypes)]) { for (size_t i = 0; i < sizeof...(TTypes); ++i) { m_tuple[i] = std::move(input[i]); } } public: template <typename TTag, typename TVal> auto Set(TVal&& val) && { using namespace NSMUltiTypeDict; constexpr static size_t TagPos = Tag2ID<TTag, TParameters...>; using rawVal = std::decay_t<TVal>; rawVal* tmp = new rawVal(std::forward<Tval>(val)); m_tuple[TagPos] = std::shared_ptr<void>(tmp, [](void* ptr) { rawVal* ntpr = static_cast<rawVal*>(ptr); delete nptr; }); using new_type = NewTupleType<rawVal, TagPos, Values<>, TTypes...>; return new_type(std::move(m_tuple)); } template <typename TTag> auto& Get() const; private: std::shared_ptr<void> m_tuple[sizeof...(TTypes)]; }; );
代码讲解:
以下内容结合原文看!!!
这段代码实现了一个 VarTypeDict 类模板,它包含一个内嵌的 Values 类模板。让我逐步解释一下这段代码的逻辑。
首先定义了一个模板类 VarTypeDict,它接受一系列模板参数 TParameters。
在 VarTypeDict 类模板中定义了一个内嵌的 Values 类模板。Values 类模板接受一系列模板参数 TTypes。该类模板包含以下几个成员函数和数据成员:
1. 构造函数 Values() = default; 默认构造函数。
2. 另一个构造函数 Values(std::shared_ptr<void>(&&input)[sizeof...(TTypes)]),它接受一个大小为 sizeof...(TTypes) 的 std::shared_ptr<void> 数组,将其内容逐个移动构造到 m_tuple 数组中。
3. 成员函数 template <typename TTag, typename TVal> auto Set(TVal&& val) && 用于设置特定类型的值。它首先使用 Tag2ID 获取模板参数 TTag 在 TParameters 中的位置,然后创建一个 TVal 类型的对象,并存储为 std::shared_ptr<void> 类型,最后使用 NewTupleType 创建一个新的 Values 类型,并返回该类型的对象。
4. 成员函数 template <typename TTag> auto& Get() const; 用于获取特定类型的值。
5. 数据成员 std::shared_ptr<void> m_tuple[sizeof...(TTypes)] 用于存储元组中的值。
值得注意的是,代码中使用了一些未定义的模板类型和函数,比如 NSMUltiTypeDict、Tag2ID、NewTupleType 等,这些部分的定义未在提供的代码片段中。因此,要完整理解这段代码的逻辑,还需要查看这些部分的定义。
综上所述,这段代码实现了一个基于模板的多类型字典,可以动态存储和获取多种类型的值。通过 Values 类模板的成员函数 Set 和 Get,可以实现对值的设置和获取操作。
3.1 Get接口实现以及分析
代码示例:
template <typename TTag> auto& Get() const { using namespace NSMUltiTypeDict; constexpr static size_t TagPos = Tag2ID<TTag, TParameters...>; return *static_cast<std::decay_t<typename GetTupleType<TagPos, TTypes...>::type>*>(m_tuple[TagPos].get()); }
Get接口代码逻辑:
1. Get 函数是一个模板成员函数,它接受模板参数 TTag,在编译期使用 Tag2ID 获取模板参数 TTag 在 TParameters 中的位置,存储在 TagPos 中。
2. 接着使用 GetTupleType 获取在 Values 类模板的 Types 参数包中第 TagPos 个类型对应的类型,通过 std::decay_t 去除引用和 cv 限定符。
3. 然后通过 m_tuple[TagPos] 获取对应位置的值,使用 get() 函数获取 std::shared_ptr 指向的原始指针,进行 static_cast 转换,并通过解引用运算符 * 返回对应类型的引用。
总结来说,Get 函数的目的是根据传入的类型标签 TTag,在运行时获取对应类型的值,并返回对应类型的引用。这样就实现了通过类型标签快速获取特定类型的值的功能。
3.1 Tag2ID逻辑分析
NewTupleType逻辑分析请看原文,以下内容结合原文看!!!
template <typename TTag, typename... TParameters> constexpr size_t Tag2ID() { return Tag2IDImpl<TTag, 0, TParameters...>(); } template <typename TTag, size_t Index, typename TParameter, typename... TParameters> constexpr size_t Tag2IDImpl() { if constexpr (std::is_same_v<TTag, TParameter>) { return Index; } else { return Tag2IDImpl<TTag, Index + 1, TParameters...>(); } } template <typename TTag, size_t Index> constexpr size_t Tag2IDImp() { // 没有找到匹配的类型标签,可能需要抛出错误或返回一个默认值 return static_cast<size_t>(-1); }
上述代码使用递归实现了 Tag2ID 函数。首先调用了 Tag2ID 函数模板,并传入类型标签和类型列表作为模板参数。然后,调用了 Tag2IDImpl 函数模板,传入类型标签、当前索引为0以及类型列表作为模板参数。
在 Tag2IDImpl 函数模板中,首先检查当前类型标签(TTag)是否与当前类型参数(TParameter)相同。如果相同,则返回当前索引值作为类型标签在类型列表中的位置。否则,递归调用 Tag2IDImpl 函数模板,并将索引值加1,继续匹配下一个类型参数。
如果遍历完整个类型列表都没有找到匹配的类型标签,那么可能需要采取适当的错误处理,如抛出异常或返回一个默认值。
总之,Tag2ID 函数的目的是根据给定的类型标签和类型列表,返回类型标签在类型列表中的位置。这个位置可以用于进一步操作,如在代码中获取对应位置的值或执行其他相关操作。