Chapter 2 Class Templates: Derivation of 2.9 Class Template Arguments

2.9 Class Template Argument Deduction

Derivation of 2.9 template actual parameters

 

Until C++17, you always had to pass all template parameter types to class templates(unless they have default values). Since C++17, the constraint that you always have to specify the template arguments explicitly was relaxed. Instead, you can skip to define the templates arguments explicitly, if the constructor is able to deduce all template parameters (that don’t have a default value),

Before C ++ 17, you always had to pass all template arguments to the class template (unless they had default values). Starting from C ++ 17, the restriction of explicitly specifying template arguments has been relaxed. On the contrary, if the constructor can derive all template parameters (these parameters do not contain default values), then the definition of template actual parameters can be ignored,

 

For example, in all previous code examples, you can use a copy constructor without specifying the template arguments:

For example, in all the previous code examples, when using copy construction, you can not specify template arguments:

Stack < int > intStack1; // stack of strings 
Stack < int > intStack2 = intStack1; // OK: suitable for all versions 
Stack intStack3 = intStack1; // OK: start from C ++ 17

By providing constructors that pass some initial arguments, you can support deduction of the element type of a stack. For example, we could provide a stack that can be initialized by a single element:

By providing a constructor for passing some initial parameters, you can support the derivation of stack element types. For example, we can provide a stack initialized by a single element.

template<typename T>
class Stack {
private:
    std::vector<T> elems; // 元素
public:
    Stack() = default;
    Stack (T const & elem) // Initialize the stack with an element (therefore, elems has an element)

        : element ({element}) {
    }
};

This allows you to declare a stack as follows:

This allows you to declare a stack as follows:

Stack intStack = 0 ; // From C ++ 17, it is derived as Stack <int>

 

By initializing the stack with the integer 0, the template parameter T is deduced to be int, so that a Stack<int> is instantiated.

By using the integer 0 to initialize the stack, the template parameter T can be derived as int. This instantiates a Stack <int>.

 

Note the following:

Note the following:

    • Due to the definition of the int constructor, you have to request the default constructors to be available with its default behavior, because the default constructor is available only if no other constructor is defined:

    Since the int constructor is defined, you must require that the default behavior of the default constructor is available. Because in the case of defining other constructors, the default constructor is not provided:

Stack() = default;

    • The argument elem is passed to elems with braces around to initialize the vector elems with an initializer list with elem as the only argument:

    The actual parameter elem is passed to the elems through braces, and the vector of elems is initialized with an initialization list with only one elem element.

: elems ({elem})   // At this time, elems has only one element (elem).

There is no constructor for a vector that is able to take a single parameter as initial element directly.

Vector does not directly accept a single parameter as the constructor of the initial element.

 

Note that, unlike for function templates, class template arguments may not be deduced only partially (by explicitly specifying only some of the template arguments). See Section 15.12 on page 314 for details.

Note that unlike function templates, class template parameters cannot be derived only partially (only some template arguments are explicitly specified). For details, see section 15.12 on page 314.

 

Class Template Arguments Deduction with String Literals

Derive class template arguments with string literals

 

In principle, you can even initialize the stack with a string literal:

In principle, you can even use string literals to initialize the stack.

Stack stringStack = " bottom " ; // From C ++ 17, it is derived as Stack <char const [7]>

But this causes a lot of trouble: In general, when passing arguments of a template type T by reference, the parameter doesn’t decay, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. This means that we really initialize a Stack< char const[7]> and use type char const[7] wherever T is used. For example, we may not push a string of different size, because it has a different type. For a detailed discussion see Section 7.4 on page 115.

But this will bring a lot of trouble: usually, when the actual parameter of the template type T is passed by reference, the type of the actual parameter will not degenerate (decay), the term "decay" here refers to the "original array type Into the corresponding original pointer type "mechanism. (Annotation: The string literal is an array type, when the array is passed by reference, it will be deduced to the "array reference" type, and will not degenerate into a pointer)

 

However, when passing arguments of a template type T by value, the parameter decays, which is the term for the mechanism to convert a raw array type to the corresponding raw pointer type. That is, the call parameter T of the constructor is deduced to be char const* so that the whole class is deduced to be a Stack<char const*>.

However, if the actual parameter of the template type T is passed by value, the actual parameter type will degenerate (the term "decay" is as described above). That is to say, the calling parameter T in the derivation constructor is char const *, so that the entire class is of type Stack <char const *>.

 

For this reason, it might be worthwhile to declare the constructor so that the argument is passed by value:

For this reason, it is worthwhile to declare a constructor that passes arguments by value:

template<typename T>
class Stack {
private:
    std::vector<T> elems; // elements
public:
    Stack (T elem) // Pass an element to initialize the stack by value 
        : elems ({elem}) { // In order to degenerate the class template (decay) 
    }
};

With this, the following initialization works fine:

In this way, the following initialization works normally:

Stack stringStack = " bottom " ; // From C ++ 17, it is derived as Stack <char const *>

In this case, however, we should better move the temporary elem into the stack to avoid unnecessarily copying it:

However, in this case, we better move the elem temporary object to the stack to avoid unnecessary copying:

template<typename T>
class Stack {
private:std::vector<T> elems; // elements
public:
    Stack(T elem) // initialize stack with one element by value
        : elems({ std::move(elem) }) {
    }
};

 

Deduction Guides

Derivation wizard

 

Instead of declaring the constructor to be called by value, there is a different solution: Because handling raw pointers in containers is a source of trouble, we should disable automatically deducing raw character pointers for container classes.

In addition to declaring the constructor to pass parameters by value, there is a different solution: because handling raw pointers in the container is the source of the problem, we should disable the container's ability to automatically derive raw character pointers.

You can define specific deduction guides to provide additional or fix existing class template argument deductions. For example, you can define that whenever a string literal or C string is passed, the stack is instantiated for std::string:

You can define a special "derivation wizard" to provide more or fix the derivation of existing class template parameters. For example, you can define that whenever a string literal or C string is passed, it is instantiated as a std :: string stack.

Stack( char const*) -> Stack<std::string>;

 

This guide has to appear in the same scope (namespace) as the class definition.

The wizard must appear in the same scope (namespace) as the class definition.

 

Usually, it follows the class definition. We call the type following the -> the guided type of the deduction guide.

Usually, it follows the class definition. We call the type that follows "->" the type of guide "Derivation Guide".

 

Now, the declaration with

Now, the statement is

Stack stringStack{"bottom"}; // OK: Stack<std::string> deduced since C++17

deduces the stack to be a Stack<std::string>.

The stack will be deduced to the type Stack <std :: string>.

 

However, the following still doesn’t work:

However, the following is still invalid:

Stack stringStack = " bottom " ; // Derived as Stack <std :: string>, but still invalid.

We deduce std::string so that we instantiate a Stack<std::string>:

We derive std :: string to instantiate a Stack <std :: string>

class Stack {
private:
    std::vector<std::string> elems; // elements
public:
    Stack(std::string const& elem) // initialize stack with one element
        : elems({ elem }) {
    }
};

However, by language rules, you can’t copy initialize (initialize using =) an object by passing a string literal to a constructor expecting a std::string. So you have to initialize the stack as follows:

However, according to language rules, you cannot pass string literals to the constructor that requires std :: string by copy initialization (using "=" initialization). Therefore, you must initialize the stack as follows (Annotation: direct initialization and copy initialization are different, see the "programming experiment" later in the article):

Stack stringStack { " bottom " }; // Derived as Stack <std :: string>, and it is valid.

Note that, if in doubt, class template argument deduction copies. After declaring stringStack as Stack<std::string> the following initializations declare the same type (thus, calling the copy constructor) instead of initializing a stack by elements that are string stacks:

Note that if in doubt, use a copy of the class template argument derivation. After declaring stringStack as a Stack <std :: string> type, the following initialization declarations are all of the same type. Therefore, initialize the stack by calling the copy constructor instead of the stack elements.

Stack stack2 {stringStack}; // Derived as Stack <std :: string> 
Stack stack3 (stringStack); // Derived as Stack <std :: string> 
Stack stack4 = {stringStack}; // Derived as <std :: string >

See Section 15.12 on page 313 for more details about class template argument deduction.

For more detailed information on the derivation of class template arguments, see section 15.12 on page 313.

[Programming experiment] derivation wizard

#include <iostream>
#include <vector>
using namespace std;

class Test
{
public:

    Test(const std::string){}
};

template<typename T>
class Stack {
private:std::vector<T> elems; // elements
public:
    Stack() = default;

    Stack ( const T & elem) // Note that this can be passed by reference! 
        : elems ({elem}) {
    }
    
    //Stack(const char* elem){}
};

Stack ( const  char *)-> Stack <std :: string >; // Derivation Wizard

int main ()
{
    / * ***** The difference between direct initialization and copy initialization ***** * / 
    // 1. Direct initialization: will convert "abcd" to string first, then pass in Test (string)
     // 2. Copy The implicit conversion requirements for initialization: from "abcd" to Test must be directly generated, there can be no other temporary objects (such as string) in the middle. 

    Test t1 ( " abcd " ); // OK
    
    // Test t2 = "abcd"; // ERROR, the copy initialization request can be directly converted from "abcd" to Test. That is, it cannot be converted into a string before generating a Test.
                       // To achieve direct conversion, you need to call Test (const char *), but because this class does not provide such a constructor,
                        // the compilation fails.
    

    / * ***** Derivation Wizard ***** * /     
    // Stack st1 = "abcd";   // The derivation is of type Stack <std :: string>, but it cannot be compiled.
                           // Here, due to language characteristics, when copying and initializing (using "=") to initialize)
                            // string literal (const char * type) cannot be passed to the constructor of Stack (std :: string)
                           // If you add a Stack (const char *) constructor to Stack <String>, you can compile it. Principle
                            // see the construction of the t1 object. 

    Stack st2 ( " abcd " );   // OK, Stack (const char *)-> Stack (std :: string) 
    Stack st3 { " abcd " }; // OK, ibid.

    // Through the copy, call the copy constructor to initialize Stack <std :: string> 
    Stack st4 = st2; // OK, call the copy constructor of 
    Stack Stack st5 (st2);   // Same as 
    Stack st6 {st2}; // Same as above

    return 0;
}

Guess you like

Origin www.cnblogs.com/5iedu/p/12709243.html