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

// print_string.cpp #include "print_string.h"

template void print_string(const char*); template void print_string(const wchar_t*);

Because print_string<char> and print_string<wchar_t> are explicitly instantiated in print_string.cpp, the linker will be able to find them even though the print_string template is not defined in the header. If these explicit instantiation declarations were not present, a linker error would likely occur. See Why can templates only be implemented in the header file?

Version ≥ C++11

If an explicit instantiation definition is preceded by the extern keyword, it becomes an explicit instantiation declaration instead. The presence of an explicit instantiation declaration for a given specialization prevents the implicit instantiation of the given specialization within the current translation unit. Instead, a reference to that specialization that would otherwise cause an implicit instantiation can refer to an explicit instantiation definition in the same or another TU.

foo.h

#ifndef FOO_H #define FOO_H

template <class T> void foo(T x) { // complicated implementation

}

#endif

foo.cpp

#include "foo.h"

// explicit instantiation definitions for common cases template void foo(int);

template void foo(double);

main.cpp

#include "foo.h"

// we already know foo.cpp has explicit instantiation definitions for these extern template void foo(double);

int main() {

foo(42); // instantiates foo<int> here;

// wasteful since foo.cpp provides an explicit instantiation already! foo(3.14); // does not instantiate foo<double> here;

// uses instantiation of foo<double> in foo.cpp instead

}

Section 77.9: Non-type template parameter

Apart from types as a template parameter we are allowed to declare values of constant expressions meeting one of the following criteria:

integral or enumeration type,

pointer to object or pointer to function,

lvalue reference to object or lvalue reference to function,

pointer to member, std::nullptr_t.

GoalKicker.com – C++ Notes for Professionals

421

Like all template parameters, non-type template parameters can be explicitly specified, defaulted, or derived implicitly via Template Argument Deduction.

Example of non-type template parameter usage:

#include <iostream>

 

template<typename T, std::size_t size>

 

std::size_t size_of(T (&anArray)[size])

// Pass array by reference. Requires.

{

// an exact size. We allow all sizes

return size;

// by using a template "size".

}

 

int main()

{

char anArrayOfChar[15];

std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "\n";

int anArrayOfData[] = {1,2,3,4,5,6,7,8,9};

std::cout << "anArrayOfData: " << size_of(anArrayOfData) << "\n";

}

Example of explicitly specifying both type and non-type template parameters:

#include <array> int main ()

{

std::array<int, 5> foo; // int is a type parameter, 5 is non-type

}

Non-type template parameters are one of the ways to achieve template recurrence and enables to do Metaprogramming.

Section 77.10: Declaring non-type template arguments with auto

Prior to C++17, when writing a template non-type parameter, you had to specify its type first. So a common pattern became writing something like:

template <class T, T N> struct integral_constant {

using type = T;

static constexpr T value = N;

};

using five = integral_constant<int, 5>;

But for complicated expressions, using something like this involves having to write decltype(expr), expr when instantiating templates. The solution is to simplify this idiom and simply allow auto:

Version ≥ C++17

template <auto N>

struct integral_constant { using type = decltype(N);

static constexpr type value = N;

};

GoalKicker.com – C++ Notes for Professionals

422

using five = integral_constant<5>;

Empty custom deleter for unique_ptr

A nice motivating example can come from trying to combine the empty base optimization with a custom deleter for unique_ptr. Di erent C API deleters have di erent return types, but we don't care - we just want something to work for any function:

template <auto DeleteFn> struct FunctionDeleter { template <class T>

void operator()(T* ptr) const { DeleteFn(ptr);

}

};

template <T, auto DeleteFn>

using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

And now you can simply use any function pointer that can take an argument of type T as a template non-type parameter, regardless of return type, and get a no-size overhead unique_ptr out of it:

unique_ptr_deleter<std::FILE, std::fclose> p;

Section 77.11: Template template parameters

Sometimes we would like to pass into the template a template type without fixing its values. This is what template template parameters are created for. Very simple template template parameter examples:

template <class T> struct Tag1 { };

template <class T> struct Tag2 { };

template <template <class> class Tag> struct IntTag {

typedef Tag<int> type;

};

int main() { IntTag<Tag1>::type t;

}

Version ≥ C++11

#include <vector> #include <iostream>

template <class T, template <class...> class C, class U> C<T> cast_all(const C<U> &c) {

C<T> result(c.begin(), c.end()); return result;

}

int main() {

std::vector<float> vf = {1.2, 2.6, 3.7}; auto vi = cast_all<int>(vf);

for(auto &&i: vi) {

std::cout << i << std::endl;

}

GoalKicker.com – C++ Notes for Professionals

423

}

Section 77.12: Default template parameter value

Just like in case of the function arguments, template parameters can have their default values. All template parameters with a default value have to be declared at the end of the template parameter list. The basic idea is that the template parameters with default value can be omitted while template instantiation.

Simple example of default template parameter value usage:

template <class T, size_t N = 10> struct my_array {

T arr[N];

};

int main() {

/* Default parameter is ignored, N = 5 */ my_array<int, 5> a;

/* Print the length of a.arr: 5 */

std::cout << sizeof(a.arr) / sizeof(int) << std::endl;

/* Last parameter is omitted, N = 10 */ my_array<int> b;

/* Print the length of a.arr: 10 */

std::cout << sizeof(b.arr) / sizeof(int) << std::endl;

}

GoalKicker.com – C++ Notes for Professionals

424