Effective Standard C Library: Explicit Function Template Argument Specification and Stl A New Language Feature and ITS Impact ON Old Programming Techniques
Klaus Kreft and an Agelika Langer
http://www.cuj.com/experts/1812/langer.htm?topic=experts
In this column, we will explain how the C language on the new characteristics declared by the explicit function template parameters broke the code before it appears. In order to avoid potential problems, new programming is needed. We will study the effects of true world examples from STL. Many STL doctors are built before the new language feature is supported by the compiler, and some of the lives have not been updated, and there is still a problem with which is a function template, that is, the outer layer function template is called in the way derived by the automatic template parameter. Layer function template.
Type parameters of function templates
The template parameter of the function template explicitly declares a relatively new language characteristic, which is incremented in the C standardization process. When you read ARM (Annotated Reference Manual) [Note 1], since it explains the quasi-standard standard (Pre-Standard C ), you will find which type of initial no way to tell the compiler as a template parameter to instantiate function template . At that time, the following template is illegal.
Template
T * Create ();
Each template parameter, such as the above type parameter T, is required to be used for the [Function] parameter type of the function template. Otherwise, the compiler cannot derive the template parameters. In the above example, the function does not have any parameters, so the compiler cannot know which type to instantiate the function template.
New language features
Today, we can explicitly tell the compiler which type must be used to instantiate the function template. In the above example, we can call the function with the grammar declared by explicit parameters, as shown below:
INT N = CREATE
C language calls Create
Even when the compiler can derive the actual template parameters from the actual parameter of the function, we can also skip automatic derivation, and use explicitly declaration. This is example:
Template
Void Add (t t);
Add ("log.txt"); // Automatic Argument Deduction
Add
This example also reveals that automatic derivation and explicit statements have different effects. Automatic parameter derivation will result in instantiation of add
trap
The new language characteristics are added to solve the problem of instantification function templates when the template parameters are not used for function parameter types. It has added additional flexibility for languages, but there is a trap. Previously safe code may now have problems. Before the explicit function template parameters declared, there is a full reason to implement a function template, and it is transmitted to other function templates by automatic parameters, as shown below:
Template
Template
Void Outer (t t)
{...
Inner (t);
...
}
The outer function template passes its function parameters to the inner layer function template, and to call the inner layer function template, it allows the compiler to calculate the template parameters.
Now, there is an explicit function template parameter declaration, which is a problem with a function template implementation, because if the outer function is instantiated with reference type, it may cause object cutting problems (Object Slicing Problem, or "object slice problem"). There is still reason to pass the parameters from a function template to another function template, but now its security implementation has been different.
Automatic function template parameter derived vs. Explicit function template parameters declaration
Let us analyze the examples of problems above:
Template
Void Inner (t t);
Template
Void Outer (t t)
{...
Inner (t);
...
}
Why is it happening now, and it is safe before the explicit parameters declared that the introduction is used to instantiate the function template? This must mention additional flexibility to the new feature to join the language.
problem
With explicit template parameters, the outer layer function template can be instantiated with a reference type, as shown below:
Class Base;
Class Derived: Public Base {};
Base & ref = new deerid;
Outer
The Generated Function Outer
Void Outer
{...
Inner (T); // Calls: void inner
...
}
When it calls the inner layer function template, it depends on the automatic template parameter derivation, and the compiler value type base rather than the reference type base & to instantiate the inner layer function template. This may be surprising, but it can be understood that the automatic function parameter derived process contains many step implicit type conversions, one is the conversion of the left value to the right value (more details of the conversion process are behind the following). The result is that the functional argument T (a reference to the base class type of derived class object) is passed to the inner layer function from the outer function in the way. Only the base type of the derived class object is visible to the inner layer function. This is called object cut, and it is a well-known problem when the creating a base type is creating a copy of the base class.
Solution
In the correct Outer () implementation, we will pass the parameter T to the inner layer function in the form (i.e., when the transmission is received, the reference is received, and the value is transmitted). This can be easily implemented by the explicit parameters of the inner layer function, as shown below:
Template
Void Inner (t t);
Template
Void Outer (t t)
{...
Inner
...
}
The resulting outer function Outer
Void Outer
{...
Inner
...
}
The function parameter t is passed to the inner layer function in the way, and does not cause the object to cut because there is no copy. Evaluation
The object cut / slice problem in the function template is derived from the fact that the template is instantiated by the base class reference type. Now, you can understand why Outer () and Inner () function templates are now secure before explicit template parameters declared that they are joined, because it is not possible to instantiate function templates with reference types. Because this simple reason, the implementation of Outer () does not need to prepare a reference to the base type as a parameter. There is no danger of object cut, because it will not encounter references. Now, this limit does not exist, the function template can be instantiated with any type, including the reference type. Therefore, the implementation of the function template must be prepared to properly handle any type.
Other possible solutions
In principle, the implementation of the outer function template can use another different way. Perhaps he / she does not want to accept any type, and determine the exclusion type instantial outer function template to limit only value type. Here is a possible implementation that cannot be instantiated by reference types:
Template
Void Inner (t t);
Template
Void Outer (t t)
{...
TYPEDEF T & DUMMY;
Inner (t);
...
}
Trying to instantiate Outer
Oid Outer
...
Typedef Base && Dummy; // Error: Reference to Reference
Inner (t);
...
The disadvantage of this solution is that we usually work to the maximum applicability of the template, rather than limiting its availability. Unless there is a convincing reason to define this more flexible solution to this more flexible parameters using explicit parameters.
Implicit type conversion during template parameters
Based on the comprehensive, it is important to point out that in the automatic parameters of our example, the conversion of the left value to the right is not the only implicit type conversion used before the template parameter.
Before deciding the template parameter type, the compiler performs the following implicit type conversion:
l Left value transformation
l Modify word conversion
l Deficed the transformation of the base class
See "C Primer" ([Note 2], P500) Full discussion on this topic.
Briefly, the compiler weakens some type of property, such as the left value of the reference type in our example. For example, the compiler is used to instantiate the function template, not the corresponding reference type. Similarly, it uses a pointer type to instantiate the function template instead of the corresponding array type. It removes const modifications, never instantiate the function template with a const type, always uses the corresponding non-const type.
The bottom line is: Automatic template parameter derived contains type conversion, and some type properties will be lost when the compiler automatically determines the template parameter. These types of properties can be reserved when using explicit function template parameters.
STL generic algorithm
The function template for calling the inner layer function template in a way that detritions the template parameters can be found in many STL works. All generic algorithms in STL are function templates, and they often use other generic algorithms in their own implementation. The REMOVE_IF () generic algorithm is an example. This is the implementation of possible discovery in popular STL works:
Template
ForwardITerator Remove_if (ForwardITerator First, ForwardITerator Last, Predicate Pred) {
First = Find_IF (First, Last, PRED);
Forwarditerator next = first;
Return first == Last? First: remove_copy_if ( next, last, first, pred);
}
REMOVE_IF () algorithm calls Find_IF () and remove_copy_copy_if (). For both, remove_if () relies on automatic parameters. Iterator and Predicate are passed by values without taking into account the fact that they may pass into Remove_IF () in the way.
In this case, is there an object cut? Do we often pass the Iterator and Predicate in the way?
Iterators. Ok, Iterator is specified as a value semantics. The Iterator type must be copyable; therefore, the pass value is guaranteed. Typically, the Iterator type does not contain many data and there is no virtual function; it is therefore uncommon to reference.
Predicate. Different from the requirements of Predicate. The requirements for the standard pair of Predicate are relatively relaxed. This is a quoted from the C standard:
The PREDICATE parameter is used for every time the generic algorithm expects a function to act on the corresponding Iterator and returns a value that can be tested with TRUE. In other words, if a generic algorithm accepts a Predicate parameter PRED and ITERATOR parameter first, in the constructor, it should work correctly: (PRED (* first)) {...}. Functor object PRED should not apply any non-Const functions on the Iterator's reverse reference. This functor can be a pointer to a function, or an object with a suitable call operation operator ().
With popular words, the type of Predicate is either a function pointer type or a functor type. Functions (or objects) must return a return value that can be converted to the BOOL, and must accept the parameters of the type of Iterator to convert. In addition, PREDICATE must never modify the elements in the container. In addition, the standard has no further requirements for the Predicate type. Note that preIDCATE does not even need to copy.
Predicate with count_if ()
This relatively weak requirement for PrediacT is indeed enough. Typically, the generic algorithm does not have to do too much things: it is just a reference to elements in a container (by reverse reference an item) to call Prediacte. This is a typical example, a count_if () algorithm, showing how generic algorithms use its Predicate:
Template
TypeName Iterator_Traits
Count_if (InputITerator First, InputITerator Last, Predicate Pred) {
TypenAme Iterator_Traits
IF (PRED (* first))
N;
Return n;
}
The generic algorithm only invokes Predicate, providing an end of the Iterator as an argument, and the return value of the Predicate is used in the conditional expression.
Predicate and remove_if ()
Relatively, the implementation of the Remove_IF () algorithm shown in the previous article is much more than the requirements of its Predicate. It passes the Predicate to other generic algorithms in a pass value, first requiring the Predicate type to be copied, and the risk of happens when the reference to the base class type of the PREDICATE.
Polymorphic predicate type
To illustrate the potential object cutting problem, an inheritance system of a Predicate is envisaged, with an abstract base class and a lot of derived physical classes [Note 3]. If you want to use it in a STL generic algorithm, you may try to instantiate the STL generic algorithm with reference types of base classes. The following code demonstrates this process:
Template
Void foo (Container & Cont,
Const PredicateBase
{
REMOVE_IF Const PredicateBase Cont.begin (), cont.end (), pred); } The generated REMOVE_IF () function accepts Predicate by reference to the reference class, as seen from the Remove_if () implementation, passes it to Find_IF () and Remove_copy_IF () in the way the value is transmitted, typically Object cutting problem [Note 4]. Predicate type containing data Another reason for the reference to PREDICATE is that Predicate has a data member and accumulates information with these data members. Consider a bank program, we have a list of bank accounts, and you need to check if the account balance is below a limit; if it is lower, the customer will be removed from the account list. At the same time, whenever the balance exceeds a limit, the customer's name is added to a mailing list. We can complete this task with remove_if () by a suitable predicate, and this predicate establishes a mailing list and returns True on the client that must be removed. There is only one minimal problem: How do we get access to this list after the list of posts is a Predicate's data member? When Predicate is transmitted to remove_if () in a value transfer (), the generic algorithm works on a temporary copy of our Predicate object, and all accumulated information is discarded before we have the opportunity to analyze. Because of this reason, we pass it in a bi-reference method, but then generic algorithm passes it to find_if () and remove_copy_if () in the way. And destroy the purpose of the previous use reference. to sum up There are various reasons to pass the functionor to the generic algorithm in a manner requiring the reference. Unfortunately, some standard runtime creates a copy of the reference object and takes the risk of object cut because they assume that the generic algorithm will never be instantiated with reference types. This STL problem is an educational example that tells about how the language is expanded to suddenly require new programming. Today, we cannot make any security assumptions for the template parameters of the instantiation function template. They can be a reference type, which can have const modifiers, which may have other (not available during automatic template parameters). When we pass the "unknown" type of function parameters to the inner layer function template, we can avoid object cutting problems with the two methods discussed herein: L is restricted. If we intend to strengthen the limitations of the type parameters of the template, we have to document these restrictions, and we should perfectly ensure that the template does not instantiate the type of unquenque. In our example, a dumb Typedef will lead to references to undesirable reference types, which achieves the expected results. l Hold neutral. Typically, we work hard to maximize the maximum availability of the template and avoid any restrictions, not losing any type of properties, passing the parameters of the function template, as long as the inner layer function template is called by the explicit function template parameter. Quote and notes [1] Margaret A. Ellis and Bjarne Stroustrup. The Annotated C Reference Manual (Addison-Wesley, 1990). [2] Stan Lippman and Josée Lajoie. The C Primer (Addison-Wesley, 1998). [3] Hierarchies of polymorphic predicate types can be found in practice because the GOF book [5] suggests this kind of implementation for the Strategy pattern. Predicates in STL are typical strategies in the sense of the GOF strategy pattern. [4] One might argue that use of polymorphic predicate types in conjunction with STL is not a wise thing to do. Generic programming provides enough alternatives (replace run-time by compile-time polymorphism), and there is no need for passing predicate base . [5] Gamma, Helm, Johnson, Vlissides. Design Patterns (Addison-Wesley, 1995).