c++ 模板类型萃取技术
Templates are a useful feature with which we can define a generic function or a generic class that can be used with different types. To use the templates function, we need to specify the concrete types as the templates arguments on each call to the function. In practice, explicitly specifying all types for template functions can quickly become tedious, so instead of specifying them explicitly, we often let compiler to automatically determine the intended types. This is called templates argument deduction.
模板是一项有用的功能,通过它我们可以定义通用函数或可以用于不同类型的通用类。 要使用模板函数,我们需要在每次调用函数时将具体类型指定为模板参数。 在实践中,为模板函数显式指定所有类型很快就会变得乏味,因此,我们通常让编译器自动确定所需的类型,而不是显式地指定它们。 这称为模板参数推导。
The basic argument deduction process compares the type of the passed argument with the corresponding templates parameter T and try to conclude the correct substitution.
基本参数推导过程将传递的参数的类型与相应的模板参数T进行比较,并尝试得出正确的替换结果。
Let’s say there is a template function defined as below, that takes two arguments and return the sum of the two values.
假设有一个如下定义的模板函数,该函数接受两个参数并返回两个值的和。
template
T add(T a, T b) {
return a + b;
}
Instead of specifying the templates arguments explicitly, we can call the function with arguments with concrete types.
无需显式指定模板参数,我们可以使用具有具体类型的参数来调用函数。
auto sum = add(1, 2); // Explicit specification
auto sum = add(1, 2); // Implicit specification
In above example, the templates parameter T is substituted as int for both explicit and implicit specification cases.
在上面的示例中,对于显式和隐式规范情况,都将模板参数T替换为int 。
This all look simple so far but sooner the users of the templates function might stumble upon unexpected errors from seemingly innocent attempt like below,
到目前为止,这一切看起来都很简单,但是模板功能的用户可能会偶然发现如下所示的无辜尝试带来的意外错误,
auto sum = add(1, 2.5); // Passing int(1) and double(2.5) as
// templates arguments##### Error #####
templates argument deduction/substitution failed.
As you can see, passing 1 (int) and 2.5 (double) as the arguments of the templates function leads to a compiler error. Each argument is analysed independently in the deduction process, and the conclusions differ in the end (T deduced as int from the 1st argument, T deduced as double from the 2nd argument), hence the deduction process fails and compiler gives an error.
如您所见,将1(int)和2.5(double)用作模板函数的参数会导致编译器错误。 每个推论在推论过程中都是独立分析的,最终的结论是不同的( T从第一个论证推导为int , T从第二个论证推导为double ),因此推论过程失败,编译器给出了错误。
类型身份习语 (Type identity idiom)
The reason the template argument deduction failed for the above function is because deduction was performed for both the first and the second arguments and the end result had conflict. One approach to remove the error is to prevent the automatic argument deduction for either the 1st or 2nd argument and only use a single deduction result to deduce the type of templates parameter T.
模板自变量推导对上述函数失败的原因是,对第一个和第二个自变量都进行了推导,并且最终结果存在冲突。 消除错误的一种方法是防止对第一个或第二个参数进行自动参数推导,并且仅使用单个推导结果来推导模板参数T的类型。
To achieve this, we can use a technique called “type identity idiom”. It can be described as below,
为此,我们可以使用一种称为“类型身份习语”的技术。 可以描述如下
template
struct type_identity {
using type = T;
};
Using the type_identity, we modify the add function like below,
使用type_identity,我们如下修改add函数,
template
T add(T a, typename type_identity::type b) {
return a + b;
}
Now, calling the function with two different types(int, double) will not give compile errors.
现在,使用两种不同类型(int,double)调用该函数将不会产生编译错误。
auto sum = add(1, 2.5) // No errors.// The templates type T is deduced as int.
If you see the resulting type, the templates type T is deduced as int and the 2nd argument was not used for type deduction by compiler.
如果看到结果类型,则将模板类型T推导为int,并且编译器未将第二个参数用于类型推导。
To understand more, Let’s take a deeper look at the new add function definition. The expression for the second argument was replaced with
要了解更多,让我们更深入地研究新的add函数定义。 第二个参数的表达式被替换为
typename type_identity::type b
typename¹ is a special keyword used to tell compiler that the following name is a type. Without this information, compiler cannot know if the type is a type or static member defined in the type_identity struct. As you can see the definition of type_identity, type is just an alias of templates parameter T.
typename¹是一个特殊的关键字,用于告诉编译器以下名称是类型。 没有这些信息,编译器将无法知道类型是type_identity结构中定义的类型还是静态成员。 如您所见, type_identity的定义, type只是模板参数T的别名。
Meanwhile, if you see the section Non-deduced contexts² in C++ documentation,
同时,如果您看到C ++文档中的“非推论上下文”一节,
In the following cases, the types, templates, and non-type values that are used to compose
P
do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.在以下情况下,用于构成
P
的类型,模板和非类型值不参与模板参数推导,而是使用在其他地方推导或明确指定的模板参数。 如果模板参数仅在未推导的上下文中使用并且未明确指定,则模板参数推导将失败。1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:
1)使用qualified-id指定的类型的嵌套名称说明符 (范围解析运算符::的所有内容):
So, put it in easier way, if template parameter T appears on the left side of :: scope resolution operator in an expression of a template function parameter, compiler does not try to deduce the type by deduction process. Instead, it uses the result of deduction process from somewhere else. In case of our add function, the deduction result for the 1st argument (that is, int) is used to substitute T in the 2nd argument. Since type is just an alias of T in type_identity struct, the type of the 2nd argument is concluded to int too.
因此,以一种更简便的方式进行说明,如果模板参数T出现在::范围解析运算符的左侧,并且该模板函数参数的表达式中,则编译器不会尝试通过推导过程来推导类型。 相反,它使用其他地方的扣除过程的结果。 在我们的add函数的情况下,第一个参数的推导结果(即int )用于替换第二个参数中的T。 由于类型仅仅是一个在TYPE_IDENTITY结构T的别名,第二个参数的类型推断为int了。
From C++20, the type_identity is included in the language and provided as std::type_identity³. Also to avoid writing ::type every time, an alias type_identity_t is available too. The definition of type_identity_t looks like below.
从C ++ 20开始, type_identity包含在语言中,并以std :: type_identity³的形式提供。 另外,为避免每次都写:: type ,也可以使用别名type_identity_t 。 type_identity_t的定义如下所示。
template< class T >
using type_identity_t = typename type_identity::type;
std :: common_type (std::common_type)
Now users of add function can call the function without explicitly specifying the type, and get no compiler errors. However, it is not very convenient. The users of the function need to know which function argument is used for the type deduction. Also the behaviour is not very intuitive. For example, it is more natural to expect that each templates parameter is substituted as double when we give int and double values as arguments. Otherwise, converting 2.5 as int loses its accuracy.
现在, add函数的用户可以在不显式指定类型的情况下调用函数,并且不会出现编译器错误。 但是,它不是很方便。 函数的用户需要知道哪个函数参数用于类型推导。 行为也不是很直观。 例如,当我们给定int和double值作为参数时,更期望每个模板参数都被替换为double 。 否则,将2.5转换为int会失去其准确性。
auto sum = add(1, 2.5) // 2.5 is converted to int, truncated to 2!
To achieve the more intuitive behaviour, we can use a feature called std::common_type⁴. std::common_type is a templates utility that gives a common type of given types that all of them can be implicitly converted to. Using std::common_type, Our final add function looks like below,
为了实现更直观的行为,我们可以使用一个名为std :: common_type feature的功能。 std :: common_type是模板实用程序,它提供给定类型的通用类型,可以将所有类型隐式转换为给定类型。 使用std :: common_type,我们的最终添加函数如下所示,
template
T add_impl(T a, T b) {
return a + b;
}template
typename std::common_type_t
add(T1 t1, T2 t2) {
return add_impl>(t1, t2);
}
The new add function takes two arguments. Templates type deduction occurs for each T1, T2. Then, add_impl function is called by explicitly specifying the templates type T, with std::common_type_t
新的add函数带有两个参数。 模板类型推导发生于每个T1 , T2 。 然后,通过显式指定模板类型T来调用add_impl函数,其中std :: common_type_t
摘要 (Summary)
In this post, I explained about the common C++ templates technique called “Type identity idiom” and useful utility called std::common_type.
在这篇文章中,我解释了称为“类型身份习语”的常见C ++模板技术以及名为std :: common_type的有用实用程序。
[1]: https://en.cppreference.com/w/cpp/keyword/typename
[1]: https : //en.cppreference.com/w/cpp/keyword/typename
[2]:https://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts
[2]: https : //en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts
[3]: https://en.cppreference.com/w/cpp/types/type_identity
[3]: https : //en.cppreference.com/w/cpp/types/type_identity
[4]: https://en.cppreference.com/w/cpp/types/common_type
[4]: https : //en.cppreference.com/w/cpp/types/common_type
1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:
1)使用qualified-id指定的类型的嵌套名称说明符 (范围解析运算符::的所有内容):
翻译自: https://levelup.gitconnected.com/c-templates-type-identity-technique-f2b427759403
c++ 模板类型萃取技术