Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
josuttis_nm_c20_the_complete_guide.pdf
Скачиваний:
44
Добавлен:
27.03.2023
Размер:
5.85 Mб
Скачать

2.2 Using auto for Parameters in Practice

27

Note that templates may not be declared inside functions. With member functions using auto parameters, you can no longer define the class or data structure locally inside a function:

void foo()

{

struct Data {

void mem(auto); // ERROR can’t declare templates inside functions

};

}

See sentinel1.cpp for an example of a member operator == with auto.

2.2Using auto for Parameters in Practice

Using auto for parameters has some benefits and consequences.

2.2.1Deferred Type Checks with auto

With auto parameters, it is significantly easier to implement code with circular dependencies.

Consider, for example, two classes that use objects of other classes. To use objects of another class, you need the definition of their type; a forward declaration is not enough (except when only declaring a reference or pointer):

class C2;

// forward declaration

class C1 {

 

public:

 

void foo(const C2& c2) const {

// OK

c2.print();

// ERROR: C2 is incomplete type

}

 

void print() const;

 

};

 

class C2 {

 

public:

 

void foo(const C1& c1) const {

 

c1.print();

// OK

}

 

void print() const;

 

};

 

While you can implement C2::foo() inside the class definition, you cannot implement C1::foo() because to check whether the call of c2.print() is valid, the compiler needs the definition of class C2.

28

Chapter 2: Placeholder Types for Function Parameters

As a consequence, you have to implement C2::foo() after the structures of both classes are declared:

class C2;

class C1 { public:

void foo(const C2& c2) const; // forward declaration void print() const;

};

class C2 { public:

void foo(const C1& c1) const { c1.print(); // OK

}

void print() const;

};

inline void C1::foo(const C2& c2) const { // implementation (inline if in header)

c2.print();

// OK

}

 

Because generic functions check members of generic parameters when the call happens, by using auto, you can just implement the following:

class C1 { public:

void foo(const auto& c2) const { c2.print(); // OK

}

void print() const;

};

class C2 { public:

void foo(const auto& c1) const { c1.print(); // OK

}

void print() const;

};

This is nothing new. You would have the same effect when declaring C1::foo() as a member function template. However, using auto makes it easier to do so.

2.2 Using auto for Parameters in Practice

29

Note that auto allows the caller to pass parameters of any type as long as the type provides a member function print(). If you do not want that, you can use the standard concept std::same_as to constrain the use of this member function for parameters of type C2 only:

#include <concepts>

class C2;

class C1 { public:

void foo(const std::same_as<C2> auto& c2) const { c2.print(); // OK

}

void print() const;

};

...

For the concept, an incomplete type works fine.

2.2.2auto Functions versus Lambdas

A function with auto parameters is different from a lambda. For example, you still cannot pass a function with auto as a parameter without specifying the generic parameter:

bool lessByNameFunc(const auto& c1, const auto& c2) {

// sorting criterion

return c1.getName() < c2.getName();

// - compare by name

}

...

std::sort(persons.begin(), persons.end(),

lessByNameFunc); // ERROR: can’t deduce type of parameters in sorting criterion

Remember that the declaration of lessByName() is equivalent to:

 

template<typename T1, typename T2>

 

bool lessByNameFunc(const T1& c1, const T2& c2) {

// sorting criterion

return c1.getName() < c2.getName();

// - compare by name

}

 

Because the function template is not called directly, the compiler cannot deduce the template parameters to compile the call. Therefore, you have to specify the template parameters explicitly when passing a function template as a parameter:

std::sort(persons.begin(), persons.end(),

lessByName<Customer, Customer>); // OK

 

By using a lambda, you can just pass the lambda as it is:

 

auto lessByNameLambda = [] (const auto& c1, const auto& c2) {

// sorting criterion

return c1.getName() < c2.getName();

// compare by name

};

 

...

 

30

Chapter 2: Placeholder Types for Function Parameters

std::sort(persons.begin(), persons.end(),

lessByNameLambda);

// OK

The reason is that the lambda is an object that does not have a generic type. Only the use of the object as a function is generic.

On the other hand, the explicit specification of an (abbreviated) function template parameter is easier:

• Just pass the specified type after the function name:

void printFunc(const auto& arg) {

...

}

printFunc<std::string>("hello"); // call function template compiled for std::string

• For a generic lambda, we have to do the following:

auto printFunc = [] (const auto& arg) {

...

};

printFunc.operator()<std::string>("hello"); // call lambda compiled for std::string

For a generic lambda, the function call operator operator() is generic. Therefore, you have to pass the requested type as an argument to operator() to specify the template parameter explicitly.

2.3auto for Parameters in Detail

Let us look at some aspects of abbreviated function templates in detail.

2.3.1 Basic Constraints for auto Parameters

Using auto to declare a function parameter follows the same rules as using it to declare a parameter of a lambda:

For each parameter declared with auto, the function has an implicit template parameter.

The parameters can be a parameter pack:

void foo(auto... args);

This is equivalent to the following (without introducing Types): template<typename... Types>

void foo(Types... args);

• Using decltype(auto) is not allowed.

Abbreviated function templates can still be called with (partially) explicitly specified template parameters. The template parameters have the order of the call parameters.

2.3 auto for Parameters in Detail

31

For example:

void foo(auto x, auto y)

{

...

}

foo("hello", 42); foo<std::string>("hello", 42); foo<std::string, long>("hello", 42);

//x has type const char*, y has type int

//x has type std::string, y has type int

//x has type std::string, y has type long

2.3.2Combining Template and auto Parameters

Abbreviated function templates can still have explicitly specified template parameters. The generated template parameters for the placeholder types are added after the specified parameters:

template<typename T>

void foo(auto x, T y, auto z)

{

...

}

 

foo("hello", 42, '?');

// x has type const char*, T and y are int, z is char

foo<long>("hello", 42, '?');

// x has type const char*, T and y are long, z is char

Therefore, the following declarations are equivalent (except that you have no name for the types where auto is used):

template<typename T>

void foo(auto x, T y, auto z);

template<typename T, typename T2, typename T3> void foo(T2 x, T y, T3 z);

As we introduce later, by using concepts as type constraints, you can constrain placeholder parameters as well as template parameters. Template parameters may then be used for such a qualification.

For example, the following declaration ensures that the second parameter y has an integral type and that the third parameter z has a type that can be converted to the type of y:

template<std::integral T>

void foo(auto x, T y, std::convertible_to<T> auto z)

{

...

}

 

foo(64, 65, 'c');

// OK, x is int, T and y are int, z is char

foo(64, 65, "c");

// ERROR: "c" cannot be converted to type int (type of 65)

foo<long,char>(64, 65, 'c');

// NOTE: x is char, T and y are long, z is char

Note that the last statement specifies the type of the parameters in the wrong order.