Ricerca…


introduzione

Classi, funzioni e variabili (dal C ++ 14) possono essere modellate. Un modello è un pezzo di codice con alcuni parametri gratuiti che diventeranno una classe, una funzione o una variabile concreta quando tutti i parametri sono specificati. I parametri possono essere tipi, valori o modelli stessi. Un modello ben noto è std::vector , che diventa un tipo di contenitore concreto quando viene specificato il tipo di elemento, ad esempio, std::vector<int> .

Sintassi

  • dichiarazione template < template-parameter-list >
  • export template < template-parameter-list > declaration / * fino a C ++ 11 * /
  • modello <> dichiarazione
  • dichiarazione del modello
  • dichiarazione modello esterno / * dal C ++ 11 * /
  • template < template-parameter-list > class ... ( opt ) identifier ( opt )
  • template < template-parameter-list > identificatore di classe ( opt ) = id-expression
  • template < template-parameter-list > typename ... ( opt ) identifier ( opt ) / * dal C ++ 17 * /
  • template < template-parameter-list > typename identifier ( opt ) = id-expression / * dal C ++ 17 * /
  • espressione postfissa . id-espressione del modello
  • postfix-expression -> modello id-expression
  • template nested-name-specifier simple-template-id ::

Osservazioni

Il template parole è una parola chiave con cinque significati diversi nel linguaggio C ++, a seconda del contesto.

  1. Quando viene seguito da un elenco di parametri del modello racchiuso tra <> , dichiara un modello come un modello di classe , un modello di funzione o una specializzazione parziale di un modello esistente.

    template <class T>
    void increment(T& x) { ++x; }
    
  2. Quando seguito da un <> vuoto , dichiara una specializzazione esplicita (completa) .

    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. Quando seguito da una dichiarazione senza <> , forma una dichiarazione o una definizione di istanziazione esplicita .

    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. All'interno di un elenco di parametri del modello, introduce un parametro del modello di modello .

    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. Dopo l'operatore di risoluzione dell'ambito :: e gli operatori di accesso dei membri della classe . e -> , specifica che il seguente nome è un modello.

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

Prima del C ++ 11, un modello poteva essere dichiarato con la parola chiave export , trasformandolo in un modello esportato . Non è necessario che la definizione di un modello esportato sia presente in ogni unità di traduzione in cui il modello viene istanziato. Ad esempio, avrebbe dovuto funzionare quanto segue:

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
}

A causa della difficoltà di implementazione, la parola chiave export non è stata supportata dalla maggior parte dei compilatori principali. È stato rimosso in C ++ 11; ora è illegale usare la parola chiave export . Invece, di solito è necessario definire i template nelle intestazioni (a differenza delle funzioni non template, che di solito non sono definite nelle intestazioni). Vedi Perché i modelli possono essere implementati solo nel file di intestazione?

Modelli di funzione

I modelli possono anche essere applicati alle funzioni (così come le strutture più tradizionali) con lo stesso effetto.

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

Questo può quindi essere usato allo stesso modo dei modelli di struttura.

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

In entrambi i casi l'argomento template viene utilizzato per sostituire i tipi dei parametri; il risultato funziona proprio come una normale funzione C ++ (se i parametri non corrispondono al tipo di template il compilatore applica le conversioni standard).

Un'ulteriore proprietà delle funzioni del modello (a differenza delle classi template) è che il compilatore può dedurre i parametri del modello in base ai parametri passati alla funzione.

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.

Questa funzione ci consente di semplificare il codice quando combiniamo strutture e funzioni del modello. C'è un modello comune nella libreria standard che ci consente di creare una template structure X usando una funzione helper 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};
}

In che modo questo aiuta?

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.

Nota: questo non è progettato per abbreviare il codice. Questo è progettato per rendere il codice più robusto. Consente di modificare i tipi modificando il codice in un singolo posto piuttosto che in più posizioni.

Inoltro di argomenti

Il modello può accettare sia lvalue che rvalue riferimenti usando il riferimento di inoltro :

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

In questo caso, il vero tipo di t sarà dedotto a seconda del contesto:

struct X { };

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

Nel primo caso, il tipo T è dedotto come riferimento a X ( X& ), e il tipo di t è il riferimento di lvalue a X , mentre nel secondo caso il tipo di T è dedotto come X e il tipo di t come riferimento di valore a X ( X&& ).

Nota: vale la pena notare che nel primo caso decltype(t) è uguale a T , ma non nel secondo.

Per poter inoltrare perfettamente t ad un'altra funzione, che si tratti di un riferimento lvalue o rvalue, si deve usare std::forward :

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

I riferimenti di inoltro possono essere utilizzati con modelli variadici:

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

Nota: i riferimenti di inoltro possono essere utilizzati solo per i parametri del modello, ad esempio, nel seguente codice, v è un riferimento di rvalue, non un riferimento di inoltro:

#include <vector>

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

Modello di classe base

L'idea di base di un modello di classe è che il parametro template venga sostituito da un tipo in fase di compilazione. Il risultato è che la stessa classe può essere riutilizzata per più tipi. L'utente specifica quale tipo verrà utilizzato quando viene dichiarata una variabile della classe. Tre esempi di questo sono mostrati 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
}

Specializzazione dei modelli

È possibile definire l'implementazione per istanze specifiche di una classe / metodo template.

Ad esempio se hai:

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

Puoi quindi scrivere:

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

Quindi un utente che scrive sqrt(4.0) otterrà l'implementazione generica mentre sqrt(4) otterrà l'implementazione specializzata.

Specializzazione di template parziale

Al contrario di una specializzazione del modello parziale, la specializzazione del modello parziale consente di introdurre template con alcuni degli argomenti del modello esistente. La specializzazione del modello parziale è disponibile solo per classi / strutture template:

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

Come mostrato sopra, le specializzazioni di template parziali possono introdurre insiemi di dati e membri di funzioni completamente diversi.

Quando viene creata un'istanza di un modello parzialmente specializzato, viene selezionata la specializzazione più adatta. Ad esempio, definiamo un modello e due specializzazioni parziali:

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

Ora le seguenti chiamate:

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

stamperà

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

I modelli di funzioni possono essere completamente specializzati:

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

Valore del parametro del modello predefinito

Proprio come nel caso degli argomenti della funzione, i parametri del modello possono avere i loro valori predefiniti. Tutti i parametri del modello con un valore predefinito devono essere dichiarati alla fine dell'elenco dei parametri del modello. L'idea di base è che i parametri del modello con valore predefinito possono essere omessi durante l'istanziazione del modello.

Semplice esempio di utilizzo del valore predefinito del parametro del modello:

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

Modello alias

C ++ 11

Esempio di base:

template<typename T> using pointer = T*;

Questa definizione rende il pointer<T> un alias di T* . Per esempio:

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

I modelli alias non possono essere specializzati. Tuttavia, tale funzionalità può essere ottenuta indirettamente facendo in modo che si riferiscano a un tipo annidato in una struttura:

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;

Parametri del modello di modello

A volte vorremmo passare al modello un tipo di modello senza fissarne i valori. Questo è ciò per cui vengono creati i parametri del modello di modello. Esempi di parametri template template molto semplici:

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

Dichiarare argomenti modello non di tipo con auto

Prima di C ++ 17, quando scrivevo un parametro non-type del modello, dovevi prima specificare il suo tipo. Quindi un modello comune è diventato scrivere qualcosa come:

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

using five = integral_constant<int, 5>;

Ma per espressioni complicate, l'uso di qualcosa di simile comporta la scrittura di decltype(expr), expr durante l'istanziazione di modelli. La soluzione è semplificare questo idioma e consentire semplicemente l' auto :

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

using five = integral_constant<5>;

Empty deleter personalizzato per unique_ptr

Un buon esempio motivante può venire dal tentativo di combinare l'ottimizzazione di base vuota con un deleter personalizzato per unique_ptr . Differenti deletatori dell'API C hanno diversi tipi di rendimento, ma non ci interessa: vogliamo solo che qualcosa funzioni per qualsiasi funzione:

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

E ora puoi semplicemente utilizzare qualsiasi puntatore a funzione che possa assumere un argomento di tipo T come parametro non di tipo template, indipendentemente dal tipo restituito, e ricavarne un overhead no-size unique_ptr :

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

Parametro modello non di tipo

A parte i tipi come parametro template, è possibile dichiarare valori di espressioni costanti che soddisfano uno dei seguenti criteri:

  • tipo integrale o di enumerazione,
  • puntatore all'oggetto o puntatore alla funzione,
  • lvalue riferimento a oggetto o lvalue riferimento alla funzione,
  • puntatore al membro,
  • std::nullptr_t .

Come tutti i parametri del modello, i parametri del modello non di tipo possono essere specificati, impostati in modo predefinito o derivati ​​in modo implicito tramite Detrazione argomento modello.

Esempio di utilizzo dei parametri del modello non di tipo:

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

Esempio di specifica esplicita dei parametri del modello di tipo e non di tipo:

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

I parametri del modello non di tipo sono uno dei modi per ottenere la ricorrenza del modello e consentono di eseguire Metaprogramming .

Strutture dati modello variabile

C ++ 14

È spesso utile definire classi o strutture che hanno un numero variabile e un tipo di membri di dati definiti in fase di compilazione. L'esempio canonico è std::tuple , ma a volte è necessario definire le proprie strutture personalizzate. Ecco un esempio che definisce la struttura usando compounding (piuttosto che ereditarietà come std::tuple . Inizia con la definizione generale (vuota), che funge anche da base per la terminazione della recrusione nella specializzazione successiva:

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

Questo ci permette già di definire una struttura vuota, DataStructure<> data , anche se non è ancora molto utile.

Segue la specializzazione del caso ricorsivo:

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

Ora è sufficiente per noi creare strutture dati arbitrarie, come DataStructure<int, float, std::string> data(1, 2.1, "hello") .

Quindi cosa sta succedendo? Innanzitutto, si noti che questa è una specializzazione il cui requisito è che esista almeno un parametro di modello variadico (vale a dire T sopra), mentre non si preoccupa del trucco specifico del pacchetto Rest . Sapere che T esiste consente, per first , la definizione del suo membro dei dati. Il resto dei dati è ricorsivamente impacchettato come DataStructure<Rest ... > rest . Il costruttore avvia entrambi i membri, inclusa una chiamata di costruttore ricorsiva al membro del rest .

Per capirlo meglio, possiamo lavorare su un esempio: supponiamo di avere una dichiarazione DataStructure<int, float> data . La dichiarazione corrisponde per prima cosa alla specializzazione, fornendo una struttura con i membri dei dati di DataStructure<float> rest int first e DataStructure<float> rest . Il rest definizione corrisponde ancora una volta questa specializzazione, creando un proprio float first e DataStructure<> rest membri. Infine, quest'ultimo rest corrisponde alla definizione del caso base, producendo una struttura vuota.

Puoi visualizzarlo come segue:

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

Ora abbiamo la struttura dei dati, ma non è ancora molto utile dato che non possiamo accedere facilmente ai singoli elementi di dati (ad esempio per accedere all'ultimo membro di DataStructure<int, float, std::string> data dovremmo utilizzare i data.rest.rest.first , che non è esattamente user-friendly). Quindi aggiungiamo un metodo get (necessario solo nella specializzazione in quanto la struttura del caso base non ha dati da get ):

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

Come potete vedere, questa funzione del membro get è a sua volta data.get<1>() su modelli - questa volta sull'indice del membro necessario (quindi l'utilizzo può essere data.get<1>() come data.get<1>() , simile a std::tuple ). Il lavoro effettivo è svolto da una funzione statica in una classe helper, GetHelper . La ragione per cui non possiamo definire la funzionalità richiesta direttamente DataStructure 's get è perché (come vedremo tra breve vedi) avremmo bisogno di specializzarsi su idx - ma non è possibile specializzare una funzione di membro template, senza che si specializza la classe contenente modello. Nota anche l'uso di un auto stile C ++ 14 qui rende le nostre vite molto più semplici, altrimenti avremmo bisogno di un'espressione piuttosto complicata per il tipo di ritorno.

Quindi alla classe helper. Questa volta avremo bisogno di una dichiarazione in avanti vuota e di due specializzazioni. Prima la dichiarazione:

template<size_t idx, typename T>
struct GetHelper;

Ora il caso base (quando idx==0 ). In questo caso, restituiamo il first membro:

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

Nel caso ricorsivo, decrementiamo idx e invochiamo GetHelper per il rest membro:

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

Per lavorare con un esempio, supponiamo di avere DataStructure<int, float> data e abbiamo bisogno di data.get<1>() . Questo richiama GetHelper<1, DataStructure<int, float>>::get(data) (la seconda specializzazione), che a sua volta richiama GetHelper<0, DataStructure<float>>::get(data.rest) , che alla fine restituisce (dalla 1a specializzazione come ora idx è 0) data.rest.first .

Quindi è così! Ecco l'intero codice di funzionamento, con alcuni esempi di utilizzo nella funzione main :

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

Istanziazione esplicita

Una definizione di istanziazione esplicita crea e dichiara una classe, una funzione o una variabile concreta da un modello, senza utilizzarlo ancora. Un'istanza esplicita può essere referenziata da altre unità di traduzione. Questo può essere usato per evitare di definire un modello in un file di intestazione, se verrà istanziato solo con un insieme finito di argomenti. Per esempio:

// 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*);

Poiché print_string<char> e print_string<wchar_t> sono esplicitamente istanziati in print_string.cpp , il linker sarà in grado di trovarli anche se il modello print_string non è definito nell'intestazione. Se queste dichiarazioni di istanziazione esplicite non fossero presenti, si verificherebbe probabilmente un errore del linker. Vedi Perché i modelli possono essere implementati solo nel file di intestazione?

C ++ 11

Se una definizione di istanziazione esplicita è preceduta dalla extern parola chiave , diventa una dichiarazione di istanza esplicita, invece. La presenza di una dichiarazione di istanziazione esplicita per una data specializzazione impedisce l'istanza implicita della specializzazione data all'interno dell'unità di traduzione corrente. Invece, un riferimento a quella specializzazione che altrimenti causerebbe un'istanza implicita può riferirsi a una definizione di istanziazione esplicita nella stessa o in un'altra 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow