Zoeken…


Invoering

Klassen, functies en (sinds C ++ 14) variabelen kunnen worden gemodelleerd. Een sjabloon is een stuk code met een aantal gratis parameters die een concrete klasse, functie of variabele worden wanneer alle parameters worden opgegeven. Parameters kunnen typen, waarden of zelf-sjablonen zijn. Een bekend sjabloon is std::vector , dat een concreet containertype wordt wanneer het elementtype wordt opgegeven, bijvoorbeeld std::vector<int> .

Syntaxis

  • template < template-parameter-list > aangifte
  • export template < template-parameter-list > aangifte / * tot C ++ 11 * /
  • sjabloon <> aangifte
  • sjabloon verklaring
  • extern sjabloon verklaring / * sinds C ++ 11 * /
  • template < template-parameter-list > class ... ( opt ) identifier ( opt )
  • template < template-parameter-list > class identifier ( opt ) = id-expressie
  • template < template-parameter-list > typename ... ( opt ) identifier ( opt ) / * sinds C ++ 17 * /
  • template < template-parameter-list > typename identifier ( opt ) = id-expression / * sinds C ++ 17 * /
  • postfix-expressie . sjabloon id-expressie
  • postfix-expression -> template id-expression
  • nested-name-specifier template simple-template-id ::

Opmerkingen

Het template is een trefwoord met vijf verschillende betekenissen in de taal C ++, afhankelijk van de context.

  1. Wanneer het wordt gevolgd door een lijst met sjabloonparameters die zijn ingesloten in <> , geeft het een sjabloon aan, zoals een klassensjabloon , een functiesjabloon of een gedeeltelijke specialisatie van een bestaande sjabloon.

    template <class T>
    void increment(T& x) { ++x; }
    
  2. Wanneer het wordt gevolgd door een lege <> , wordt een expliciete (volledige) specialisatie aangegeven .

    template <class T>
    void print(T x);
    
    template <> // <-- keyword used in this sense here
    void print(const char* s) {
        // output the content of the string
        printf("%s\n", s);
    }
    
  3. Wanneer het wordt gevolgd door een verklaring zonder <> , vormt het een expliciete instantieverklaring of definitie.

    template <class T>
    std::set<T> make_singleton(T x) { return std::set<T>(x); }
    
    template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
    
  4. Binnen een lijst met sjabloonparameters introduceert deze een sjabloonparameterparameter .

    template <class T, template <class U> class Alloc>
    //                 ^^^^^^^^ keyword used in this sense here
    class List {
        struct Node {
            T value;
            Node* next;
        };
        Alloc<Node> allocator;
        Node* allocate_node() {
            return allocator.allocate(sizeof(T));
        }
        // ...
    };
    
  5. Na de operator voor :: en de toegangsoperatoren van het klasse-lid . en -> , het geeft aan dat de volgende naam een sjabloon is.

    struct Allocator {
        template <class T>
        T* allocate();
    };
    
    template <class T, class Alloc>
    class List {
        struct Node {
            T value;
            Node* next;
        }
        Alloc allocator;
        Node* allocate_node() {
            // return allocator.allocate<Node>();       // error: < and > are interpreted as
                                                        // comparison operators
            return allocator.template allocate<Node>(); // ok; allocate is a template
            //               ^^^^^^^^ keyword used in this sense here
        }
    };
    

Vóór C ++ 11 kon een sjabloon worden gedeclareerd met het export sleutelwoord , waardoor het een geëxporteerde sjabloon werd. De definitie van een geëxporteerde sjabloon hoeft niet aanwezig te zijn in elke vertaaleenheid waarin de sjabloon wordt geïnstantieerd. Het volgende zou bijvoorbeeld moeten werken:

foo.h :

#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif

foo.cpp :

#include "foo.h"
template <class T> T identity(T x) { return x; }

main.cpp :

#include "foo.h"
int main() {
    const int x = identity(42); // x is 42
}

Vanwege de moeilijkheid van de uitvoering, de export werd zoekwoord niet ondersteund door de meeste grote compilers. Het werd verwijderd in C ++ 11; nu is het illegaal om het export sleutelwoord te gebruiken. In plaats daarvan is het meestal nodig om sjablonen in kopteksten te definiëren (in tegenstelling tot niet-sjabloonfuncties, die meestal niet in kopteksten worden gedefinieerd). Zie Waarom kunnen sjablonen alleen in het headerbestand worden geïmplementeerd?

Functiesjablonen

Templating kan ook worden toegepast op functies (evenals de meer traditionele structuren) met hetzelfde effect.

// 'T' stands for the unknown type
// Both of our arguments will be of the same type.
template<typename T>
void printSum(T add1, T add2)
{
    std::cout << (add1 + add2) << std::endl;
}

Dit kan dan op dezelfde manier worden gebruikt als structuursjablonen.

printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);

In beide gevallen wordt het sjabloonargument gebruikt om de typen parameters te vervangen; het resultaat werkt net als een normale C ++ -functie (als de parameters niet overeenkomen met het sjabloontype, past de compiler de standaardconversies toe).

Een extra eigenschap van sjabloonfuncties (in tegenstelling tot sjabloonklassen) is dat de compiler de sjabloonparameters kan afleiden op basis van de parameters die aan de functie zijn doorgegeven.

printSum(4, 5);     // Both parameters are int.
                    // This allows the compiler deduce that the type
                    // T is also int.

printSum(5.0, 4);   // In this case the parameters are two different types.
                    // The compiler is unable to deduce the type of T
                    // because there are contradictions. As a result
                    // this is a compile time error.

Met deze functie kunnen we code vereenvoudigen wanneer we sjabloonstructuren en functies combineren. Er is een gemeenschappelijk patroon in de standaardbibliotheek waarmee we template structure X met behulp van een helperfunctie make_X() .

// The make_X pattern looks like this.
// 1) A template structure with 1 or more template types.
template<typename T1, typename T2>
struct MyPair
{
    T1      first;
    T2      second;
};
// 2) A make function that has a parameter type for
//    each template parameter in the template structure.
template<typename T1, typename T2>
MyPair<T1, T2> make_MyPair(T1 t1, T2 t2)
{
    return MyPair<T1, T2>{t1, t2};
}

Hoe helpt dit?

auto val1 = MyPair<int, float>{5, 8.7};     // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7);            // Create object using the types of the paramters.
                                            // In this code both val1 and val2 are the same
                                            // type.

Opmerking: dit is niet bedoeld om de code in te korten. Dit is ontworpen om de code robuuster te maken. Hiermee kunnen de typen worden gewijzigd door de code op één plaats te wijzigen in plaats van op meerdere locaties.

Argumenten doorsturen

Sjabloon kan zowel lvalue- als rvalue-referenties accepteren met behulp van forwarding-referentie :

template <typename T>
void f(T &&t);

In dit geval wordt het echte type t afgeleid, afhankelijk van de context:

struct X { };

X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)

In het eerste geval wordt het type T afgeleid als referentie naar X ( X& ), en het type t is waardeverwijzing naar X , terwijl in het tweede geval het type T wordt afgeleid als X en het type t als waardeverwijzing. tot X ( X&& ).

Opmerking: het is de moeite waard om op te merken dat in het eerste geval het decltype(t) hetzelfde is als T , maar niet in het tweede.

Om t perfect door te sturen naar een andere functie, of het nu een lvalue of rvalue referentie is, moet je std::forward :

template <typename T>
void f(T &&t) {
    g(std::forward<T>(t));
}

Doorstuurreferenties kunnen worden gebruikt met variadische sjablonen:

template <typename... Args>
void f(Args&&... args) {
    g(std::forward<Args>(args)...);
}

Opmerking: doorstuurreferenties kunnen alleen worden gebruikt voor sjabloonparameters, bijvoorbeeld in de volgende code is v een rvalue-referentie, geen doorstuurreferentie:

#include <vector>

template <typename T>
void f(std::vector<T> &&v);

Basic Class Template

Het basisidee van een klassensjabloon is dat de sjabloonparameter tijdens het compileren wordt vervangen door een type. Het resultaat is dat dezelfde klasse voor meerdere typen kan worden hergebruikt. De gebruiker geeft aan welk type wordt gebruikt wanneer een variabele van de klasse wordt gedeclareerd. Drie voorbeelden hiervan worden getoond in main() :

#include <iostream>
using std::cout;

template <typename T>         // A simple class to hold one number of any type
class Number {
public:
    void setNum(T n);         // Sets the class field to the given number
    T plus1() const;          // returns class field's "follower"
private:
    T num;                    // Class field
};

template <typename T>         // Set the class field to the given number
void Number<T>::setNum(T n) {
    num = n;
}

template <typename T>         // returns class field's "follower"
T Number<T>::plus1() const {
    return num + 1;
}

int main() {
    Number<int> anInt;        // Test with an integer (int replaces T in the class)
    anInt.setNum(1);
    cout << "My integer + 1 is " << anInt.plus1() << "\n";     // Prints 2

    Number<double> aDouble;   // Test with a double
    aDouble.setNum(3.1415926535897);
    cout << "My double + 1 is " << aDouble.plus1() << "\n";    // Prints 4.14159

    Number<float> aFloat;     // Test with a float
    aFloat.setNum(1.4);
    cout << "My float + 1 is " << aFloat.plus1() << "\n";      // Prints 2.4

    return 0;  // Successful completion
}

Sjabloon Specialisatie

U kunt de implementatie definiëren voor specifieke instantiaties van een sjabloonklasse / methode.

Bijvoorbeeld als u:

template <typename T>
T sqrt(T t) { /* Some generic implementation */ }

U kunt dan schrijven:

template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }

Vervolgens krijgt een gebruiker die sqrt(4.0) schrijft de generieke implementatie, terwijl sqrt(4) de gespecialiseerde implementatie krijgt.

Gedeeltelijke sjabloon specialisatie

In tegenstelling tot een volledige sjabloon specialisatie maakt gedeeltelijke sjabloon specialisatie het mogelijk om sjabloon te introduceren met enkele van de argumenten van bestaande sjabloon vast. Gedeeltelijke sjabloon-specialisatie is alleen beschikbaar voor sjabloonklasse / structs:

// Common case:
template<typename T, typename U>
struct S {
    T t_val;
    U u_val;
};

// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
    double another_value;
    int foo(double arg) {// Do something}
};

Zoals hierboven getoond, kunnen gedeeltelijke sjabloon-specialisaties compleet verschillende sets gegevens en functieleden introduceren.

Wanneer een gedeeltelijk gespecialiseerde sjabloon wordt geïnstantieerd, wordt de meest geschikte specialisatie geselecteerd. Laten we bijvoorbeeld een sjabloon en twee gedeeltelijke specialisaties definiëren:

template<typename T, typename U, typename V>
struct S {
    static void foo() {
        std::cout << "General case\n";
    }
};

template<typename U, typename V>
struct S<int, U, V> {
    static void foo() {
        std::cout << "T = int\n";
    }
};

template<typename V>
struct S<int, double, V> {
    static void foo() {
        std::cout << "T = int, U = double\n";
    }
};

Nu de volgende oproepen:

S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();

zal afdrukken

General case
T = int
T = int, U = double

Functiesjablonen zijn mogelijk alleen volledig gespecialiseerd:

template<typename T, typename U>
void foo(T t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

// OK.
template<>
void foo<int, int>(int a1, int a2) {
    std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}

void invoke_foo() {
    foo(1, 2.1); // Prints "General case: 1 2.1"
    foo(1,2);    // Prints "Two ints: 1 2"
}

// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

Standaard sjabloon parameterwaarde

Net als in het geval van de functieargumenten, kunnen sjabloonparameters hun standaardwaarden hebben. Alle sjabloonparameters met een standaardwaarde moeten aan het einde van de lijst met sjabloonparameters worden opgegeven. Het basisidee is dat de sjabloonparameters met standaardwaarde kunnen worden weggelaten tijdens het instantiëren van de sjabloon.

Eenvoudig voorbeeld van het gebruik van standaardsjabloonparameterwaarden:

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;
}

Aliasjabloon

C ++ 11

Basis voorbeeld:

template<typename T> using pointer = T*;

Deze definitie maakt pointer<T> een alias van T* . Bijvoorbeeld:

pointer<int> p = new int; // equivalent to: int* p = new int;

Aliasjablonen kunnen niet worden gespecialiseerd. Die functionaliteit kan echter indirect worden verkregen door ze naar een genest type in een struct te laten verwijzen:

template<typename T>
 struct nonconst_pointer_helper { typedef T* type; };

template<typename T>
 struct nonconst_pointer_helper<T const> { typedef T* type; };

template<typename T> using nonconst_pointer = nonconst_pointer_helper<T>::type;

Sjabloon sjabloonparameters

Soms willen we een sjabloontype in de sjabloon invoeren zonder de waarden ervan te bepalen. Hier worden sjabloonsjabloonparameters voor gemaakt. Zeer eenvoudige voorbeelden van sjabloonsjabloonparameters:

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;
}
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;
   }
}

Niet-type sjabloonargumenten declareren met auto

Vóór C ++ 17 moest u bij het schrijven van een sjabloon niet-type parameter eerst het type opgeven. Dus een algemeen patroon werd zoiets als:

template <class T, T N>
struct integral_constant {
    using type = T;
    static constexpr T value = N;
};

using five = integral_constant<int, 5>;

Maar voor gecompliceerde uitdrukkingen houdt het gebruik van zoiets in dat je decltype(expr), expr moet schrijven, decltype(expr), expr bij het instantiëren van sjablonen. De oplossing is om dit idioom te vereenvoudigen en eenvoudig auto :

C ++ 17
template <auto N>
struct integral_constant {
    using type = decltype(N); 
    static constexpr type value = N;
};

using five = integral_constant<5>;

Aangepaste deleter leegmaken voor unique_ptr

Een mooi motiverend voorbeeld kan komen van het proberen de lege unique_ptr te combineren met een aangepaste deleter voor unique_ptr . Verschillende C API-deleters hebben verschillende retourtypen, maar het kan ons niet schelen - we willen gewoon dat iets voor elke functie werkt:

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>>;

En nu kunt u eenvoudig elke functiepointer gebruiken die een argument van het type T als een sjabloon, niet-type parameter, ongeacht het unique_ptr , en er een no-size overhead unique_ptr uit halen:

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

Niet-type sjabloonparameter

Afgezien van typen als sjabloonparameter mogen we waarden van constante uitdrukkingen declareren die aan een van de volgende criteria voldoen:

  • integraal of opsommingstype,
  • aanwijzer naar object of aanwijzer naar functie,
  • waardeverwijzing naar object of waardeverwijzing naar functie,
  • wijzer naar lid,
  • std::nullptr_t .

Zoals alle sjabloonparameters, kunnen niet-type sjabloonparameters expliciet worden gespecificeerd, standaard worden gebruikt of impliciet worden afgeleid via Template Argument Deduction.

Voorbeeld van niet-type sjabloonparametergebruik:

#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";
}

Voorbeeld van het expliciet specificeren van zowel type als niet-type sjabloonparameters:

#include <array>
int main ()
{
    std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}

Niet-type sjabloonparameters zijn een van de manieren om sjabloonherhaling te bereiken en maakt het mogelijk om metaprogrammering uit te voeren .

Variadic template datastructuren

C ++ 14

Het is vaak handig om klassen of structuren te definiëren met een variabel aantal en type gegevensleden die tijdens het compileren zijn gedefinieerd. Het canonieke voorbeeld is std::tuple , maar soms is het nodig om uw eigen aangepaste structuren te definiëren. Hier is een voorbeeld dat de structuur definieert met behulp van compounding (in plaats van overerving zoals bij std::tuple . Begin met de algemene (lege) definitie, die ook dient als het basisscenario voor beëindiging van de recrusie in de latere specialisatie:

template<typename ... T>
struct DataStructure {};

Dit stelt ons al in staat om een lege structuur, DataStructure<> data te definiëren, hoewel dat nog niet erg handig is.

Vervolgens komt de recursieve case-specialisatie:

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;                                
    DataStructure<Rest ... > rest;
};

Dit is nu voldoende voor ons om willekeurige datastructuren te maken, zoals DataStructure<int, float, std::string> data(1, 2.1, "hello") .

Dus wat is er aan de hand? Merk eerst op dat dit een specialisatie is waarvan de eis is dat er ten minste één variadische sjabloonparameter (namelijk T hierboven) bestaat, zonder zich zorgen te maken over de specifieke samenstelling van de Rest van het pakket. Wetende dat T bestaat kan de definitie van de gegevenslid, first . De rest van de gegevens wordt recursief verpakt als DataStructure<Rest ... > rest . De constructor initieert beide leden, inclusief een recursieve constructoroproep naar het rest .

Om dit beter te begrijpen, kunnen we een voorbeeld doorlopen: stel dat u een aangifte DataStructure<int, float> data . De verklaring komt eerst overeen met de specialisatie, wat een structuur oplevert met int first en DataStructure<float> rest data-leden. Het rest definitie lucifers deze specialisatie, het creëren van een eigen float first en DataStructure<> rest leden. Eindelijk komt deze laatste rest overeen met de basis-casusdefinitie, waardoor een lege structuur ontstaat.

U kunt dit als volgt visualiseren:

DataStructure<int, float>
   -> int first
   -> DataStructure<float> rest
         -> float first
         -> DataStructure<> rest
              -> (empty)

Nu hebben we de gegevensstructuur, maar deze is nog niet erg handig omdat we niet gemakkelijk toegang kunnen krijgen tot de afzonderlijke gegevenselementen (bijvoorbeeld om toegang te krijgen tot het laatste lid van DataStructure<int, float, std::string> data we zouden moeten gebruiken data.rest.rest.first , wat niet bepaald gebruiksvriendelijk is). Dus voegen we er een get methode aan toe (alleen nodig in de specialisatie omdat de base-case structuur geen gegevens heeft om te get ):

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    ...
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
    ...
};

Zoals je kunt zien, is deze get lid-functie zelf sjabloon - dit keer in de index van het lid dat nodig is (dus gebruik kan dingen zijn als data.get<1>() , vergelijkbaar met std::tuple ). Het eigenlijke werk wordt gedaan door een statische functie in een GetHelper , GetHelper . De reden dat we de vereiste functionaliteit niet rechtstreeks in DataStructure 's get kunnen definiëren, is omdat (zoals we binnenkort zullen zien) we ons zouden moeten specialiseren op idx - maar het is niet mogelijk om een sjabloonlidfunctie te specialiseren zonder de bevattende klasse te specialiseren sjabloon. Let ook op het gebruik van een C ++ 14-stijl auto hier maakt ons leven aanzienlijk eenvoudiger en anders zouden we een heel ingewikkelde uitdrukking voor de return type nodig.

Dus door naar de helperklasse. Deze keer hebben we een lege voorwaartse aangifte en twee specialisaties nodig. Eerst de verklaring:

template<size_t idx, typename T>
struct GetHelper;

Nu het basisscenario (wanneer idx==0 ). In dit geval retourneren we alleen het first lid:

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

In de recursieve geval hebben we verlagen idx en beroep doen op de GetHelper voor de rest lid:

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

Stel dat we DataStructure<int, float> data en dat we data.get<1>() nodig hebben om een data.get<1>() . Dit roept GetHelper<1, DataStructure<int, float>>::get(data) (de 2e specialisatie) op, die op zijn beurt GetHelper<0, DataStructure<float>>::get(data.rest) , die uiteindelijk terugkeert (door de 1e specialisatie zoals nu idx is 0) data.rest.first .

Dus dat is het! Hier is de gehele test code die bij sommige voorbeeld gebruik in de main functie:

#include <iostream>

template<size_t idx, typename T>
struct GetHelper;

template<typename ... T>
struct DataStructure
{
};

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;
    DataStructure<Rest ... > rest;
    
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
};

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

int main()
{
    DataStructure<int, float, std::string> data(1, 2.1, "Hello");
        
    std::cout << data.get<0>() << std::endl;
    std::cout << data.get<1>() << std::endl;
    std::cout << data.get<2>() << std::endl;
    
    return 0;
}

Expliciete instantiatie

Een expliciete instantiedefinitie maakt en declareert een concrete klasse, functie of variabele uit een sjabloon, zonder deze nog te gebruiken. Naar een expliciete instantie kan vanuit andere vertaaleenheden worden verwezen. Dit kan worden gebruikt om te voorkomen dat een sjabloon in een koptekstbestand wordt gedefinieerd, als deze alleen met een eindige reeks argumenten wordt geïnstantieerd. Bijvoorbeeld:

// print_string.h
template <class T>
void print_string(const T* str);

// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);

Omdat print_string<char> en print_string<wchar_t> expliciet worden geïnstantieerd in print_string.cpp , kan de linker ze vinden, ook al is de print_string sjabloon niet in de koptekst gedefinieerd. Als deze expliciete instantieverklaringen niet aanwezig zouden zijn, zou er waarschijnlijk een linkerfout optreden. Zie Waarom kunnen sjablonen alleen in het headerbestand worden geïmplementeerd?

C ++ 11

Als er een expliciete concretisering definitie wordt voorafgegaan door het extern trefwoord , wordt het een expliciete concretisering aangifte in plaats daarvan. De aanwezigheid van een expliciete instantieverklaring voor een bepaalde specialisatie voorkomt de impliciete instantiëring van de gegeven specialisatie binnen de huidige vertaaleenheid. In plaats daarvan kan een verwijzing naar die specialisatie die anders een impliciete instantiëring zou veroorzaken, verwijzen naar een expliciete instantiëringdefinitie in dezelfde of een andere 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
}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow