Perché lo STL di C ++ è così fortemente basato su modelli? (e non su * interfacce *)

Voglio dire, a parte il suo nome obbligato (la libreria dei modelli standard) …

Inizialmente, C ++ intendeva presentare i concetti OOP in C. Ovvero: si potrebbe dire cosa un’ quadro specifica potrebbe e non potrebbe fare (indipendentemente da come lo fa) in base alla sua gerarchia di classi e classi. Alcune composizioni di abilità sono più difficili da descrivere in questo modo a causa delle problematiche dell’ereditarietà multipla, e il fatto che C ++ supporti il ​​concetto di interfacce in un modo un po ‘maldestro (rispetto a java, ecc.), Ma è lì (e potrebbe essere migliorata).

E poi i modelli sono entrati in gioco, insieme al STL. L’STL sembrava prendere i classici concetti di OOP e scovarli, usando invece i modelli.

Ci dovrebbe essere una distinzione tra i casi in cui i modelli sono usati per generalizzare i tipi in cui i tipi stessi non sono rilevanti per il funzionamento del modello (contenitori, per esempio). Avere un vector ha perfettamente senso.

Tuttavia, in molti altri casi (iteratori e algoritmi), i tipi di modelli dovrebbero seguire un “concetto” (Input Iterator, Forward Iterator, ecc.) In cui i dettagli effettivi del concetto sono definiti interamente dall’implementazione del modello funzione / class, e non dalla class del tipo usato con il modello, che è un po ‘anti-uso di OOP.

Ad esempio, puoi dire la funzione:

 void MyFunc(ForwardIterator *I); 

Aggiornamento: Poichè non era chiaro nella domanda originale, ForwardIterator è ok per essere un modello per consentire qualsiasi tipo di ForwardIterator. Il contrario sta avendo ForwardIterator come un concetto.

si aspetta un Forward Iterator solo osservando la sua definizione, dove è necessario esaminare l’implementazione o la documentazione per:

 template  void MyFunc(Type *I); 

Due affermazioni che posso fare a favore dell’utilizzo dei modelli: il codice compilato può essere reso più efficiente, compilando su misura il modello per ogni tipo utilizzato, invece di usare i vtables. E il fatto che i modelli possono essere utilizzati con tipi nativi.

Tuttavia, sto cercando una ragione più profonda per cui abbandonare l’OOP classico in favore del modello per la STL? (Supponendo che tu abbia letto fino a qui: P)

La risposta breve è “perché il C ++ è stato spostato”. Sì, alla fine degli anni ’70, Stroustrup intendeva creare un C aggiornato con funzionalità OOP, ma è passato molto tempo. Quando la lingua fu standardizzata nel 1998, non era più un linguaggio OOP. Era un linguaggio multi-paradigma. Sicuramente aveva un certo supporto per il codice OOP, ma aveva anche un linguaggio template completo per il turing, sovrapponibile alla metaprogrammazione in fase di compilazione, e le persone avevano scoperto la programmazione generica. All’improvviso, OOP non sembrava così importante. Non quando possiamo scrivere codice più semplice, più conciso e più efficiente utilizzando tecniche disponibili tramite modelli e programmazione generica.

OOP non è il Santo Graal. È un’idea carina, ed è stato piuttosto un miglioramento rispetto alle lingue procedurali negli anni ’70 quando è stato inventato. Ma onestamente non è tutto ciò che è rotto per essere. In molti casi è goffo e prolisso e in realtà non promuove codice o modularità riutilizzabili.

Ecco perché la comunità C ++ è oggi molto più interessata alla programmazione generica, e perché tutti stanno finalmente iniziando a rendersi conto che anche la programmazione funzionale è abbastanza intelligente. OOP da solo non è una bella vista.

Prova a disegnare un grafico di dipendenza di un ipotetico STL “OOP-ified”. Quante classi dovrebbero sapere l’una sull’altra? Ci sarebbero molte dipendenze. Sareste in grado di includere solo l’intestazione del vector , senza anche ottenere iterator o anche iostream ? Lo STL lo rende facile. Un vettore conosce il tipo iteratore che definisce, e questo è tutto. Gli algoritmi STL non sanno nulla . Non hanno nemmeno bisogno di includere un’intestazione iteratore, anche se accettano tutti iteratori come parametri. Quale è più modulare allora?

L’STL potrebbe non seguire le regole di OOP come lo definisce Java, ma non raggiunge gli obiettivi di OOP? Non raggiunge la riusabilità, l’accoppiamento basso, la modularità e l’incapsulamento?

E non raggiunge questi obiettivi meglio di una versione con OOP?

Per quanto riguarda il motivo per cui la STL è stata adottata nella lingua, sono successe diverse cose che hanno portato alla STL.

Innanzitutto, i modelli sono stati aggiunti a C ++. Sono stati aggiunti per lo stesso motivo per cui i farmaci generici sono stati aggiunti a .NET. Sembrava una buona idea essere in grado di scrivere cose come “contenitori di un tipo T” senza buttare via la sicurezza del tipo. Naturalmente, l’implementazione su cui si sono basati è stata molto più complessa e potente.

Poi le persone hanno scoperto che il meccanismo dei modelli che avevano aggiunto era persino più potente del previsto. E qualcuno ha iniziato a sperimentare usando i modelli per scrivere una libreria più generica. Uno ispirato dalla programmazione funzionale e uno che utilizzava tutte le nuove funzionalità del C ++.

Lo presentò al comitato linguistico del C ++, che impiegò un po ‘di tempo ad abituarsi perché sembrava così strano e diverso, ma alla fine si rese conto che funzionava meglio degli equivalenti OOP tradizionali che avrebbero dovuto includere altrimenti . Così hanno apportato alcune modifiche ad esso e l’hanno adottato nella libreria standard.

Non era una scelta ideologica, non era una scelta politica di “vogliamo essere OOP o no”, ma una scelta molto pragmatica. Hanno valutato la biblioteca e hanno visto che funzionava molto bene.

In ogni caso, entrambi i motivi che hai menzionato per favorire lo STL sono assolutamente essenziali.

La libreria standard C ++ deve essere efficiente. Se è meno efficiente, ad esempio, dell’equivalente codice C arrotolato a mano, la gente non lo userebbe. Ciò ridurrebbe la produttività, aumenterebbe la probabilità di bug e nel complesso sarebbe solo una ctriggers idea.

E l’STL deve lavorare con i tipi primitivi, perché i tipi primitivi sono tutto ciò che hai in C, e sono una parte importante di entrambe le lingue. Se l’STL non funzionasse con gli array nativi, sarebbe inutile .

La tua domanda ha una forte supposizione che l’OOP sia “il migliore”. Sono curioso di sapere perché. Chiedete perché hanno “abbandonato l’OOP classica”. Mi chiedo perché dovrebbero averlo bloccato. Quali vantaggi avrebbe avuto?

La risposta più diretta a ciò che penso tu stia chiedendo / lamentando è questa: l’assunto che C ++ sia un linguaggio OOP è un falso assunto.

C ++ è un linguaggio multi-paradigma. Può essere programmato usando i principi OOP, può essere programmato proceduralmente, può essere programmato genericamente (modelli) e con C ++ 11 (precedentemente noto come C ++ 0x) alcune cose possono anche essere programmate funzionalmente.

I progettisti di C ++ vedono questo come un vantaggio, quindi sostengono che costringendo il C ++ ad agire come un puro linguaggio OOP quando la programmazione generica risolve meglio il problema e, beh, più genericamente , sarebbe un passo indietro.

La mia comprensione è che in origine Stroustrup preferiva un design del contenitore “in stile OOP” e in effetti non vedeva nessun altro modo per farlo. Alexander Stepanov è il responsabile per la STL, e i suoi obiettivi non includevano “renderlo orientato agli oggetti” :

Questo è il punto fondamentale: gli algoritmi sono definiti su strutture algebriche. Mi ci sono voluti un altro paio di anni per capire che devi estendere la nozione di struttura aggiungendo requisiti di complessità a assiomi regolari. … Credo che le teorie iteratrici siano al centro dell’informatica come le teorie di anelli o spazi di Banach sono centrali per la matematica. Ogni volta che guardo un algoritmo proverei a trovare una struttura su cui è definito. Quindi quello che volevo fare era descrivere gli algoritmi in modo generico. Questo è quello che mi piace fare. Posso passare un mese a lavorare su un algoritmo ben noto cercando di trovare la sua rappresentazione generica. …

STL, almeno per me, rappresenta l’unico modo in cui la programmazione è ansible. È, infatti, abbastanza diverso dalla programmazione in C ++ come è stato presentato e viene ancora presentato nella maggior parte dei libri di testo. Ma, vedi, non stavo cercando di programmare in C ++, stavo cercando di trovare il modo giusto per gestire il software. …

Ho avuto molte false partenze. Ad esempio, ho passato anni a cercare di trovare un uso per l’ereditarietà e i virtuals, prima di capire perché quel meccanismo era fondamentalmente difettoso e non dovrebbe essere usato. Sono molto felice che nessuno possa vedere tutti i passaggi intermedi – molti di loro erano molto stupidi.

(Spiega perché l’ereditarietà e il virtualismo – ovvero il design orientato agli oggetti “era fondamentalmente imperfetto e non dovrebbe essere usato” nel resto dell’intervista).

Una volta che Stepanov ha presentato la sua libreria a Stroustrup, Stroustrup e altri hanno intrapreso sforzi titanici per inserirlo nello standard ISO C ++ (stessa intervista):

Il supporto di Bjarne Stroustrup è stato fondamentale. Bjarne voleva davvero STL nello standard e se Bjarne voleva qualcosa, lo capiva. … Mi ha anche costretto a fare dei cambiamenti in STL che non avrei mai fatto per nessun altro … lui è la persona più single che conosca. Lui fa le cose. Gli ci volle un po ‘per capire che cosa fosse STL, ma quando lo fece, era pronto a farcela. Ha inoltre contribuito alla STL sostenendo l’opinione che più di un modo di programmazione era valido – contro la fine di flak e hype per oltre un decennio, e perseguendo una combinazione di flessibilità, efficienza, sovraccarico e sicurezza del tipo in modelli che hanno reso ansible STL. Vorrei precisare abbastanza chiaramente che Bjarne è il preminente disegnatore di linguaggi della mia generazione.

La risposta si trova in questa intervista con Stepanov, l’autore della STL:

Sì. STL non è orientato agli oggetti. Penso che l’orientazione degli oggetti sia quasi una burla come l’Intelligenza Artificiale. Devo ancora vedere un pezzo interessante di codice che proviene da queste persone OO.

Perché un design OOP puro in una libreria Data Structure & Algorithms sarebbe meglio ?! OOP non è la soluzione per ogni cosa.

IMHO, STL è la libreria più elegante che abbia mai visto 🙂

per la tua domanda,

non è necessario il polimorfismo di runtime, è un vantaggio per STL implementare effettivamente la libreria utilizzando il polimorfismo statico, ovvero l’efficienza. Prova a scrivere un ordinamento o distanza generico o quale algoritmo si applica a TUTTI i contenitori! il tuo Sort in Java chiamerebbe le funzioni che sono dinamiche attraverso n livelli da eseguire!

Hai bisogno di cose stupide come Boxing e Unboxing per hide le brutte supposizioni dei cosiddetti linguaggi Pure OOP.

L’unico problema che vedo con STL e i modelli in generale sono i messaggi di errore terribili. Che sarà risolto usando Concepts in C ++ 0X.

Confrontando STL con le collezioni in Java è come confrontare Taj Mahal nella mia casa 🙂

i tipi di modelli dovrebbero seguire un “concetto” (Input Iterator, Forward Iterator, ecc.) dove i dettagli reali del concetto sono definiti interamente dall’implementazione della funzione / class template e non dalla class del tipo usato con il modello, che è un po ‘anti-utilizzo di OOP.

Penso che tu fraintenda l’uso previsto dei concetti da parte dei modelli. Forward Iterator, ad esempio, è un concetto molto ben definito. Per trovare le espressioni che devono essere valide affinché una class sia un Forward Iterator, e la loro semantica compresa la complessità computazionale, si guarda lo standard o su http://www.sgi.com/tech/stl/ForwardIterator.html (devi seguire i link a Input, Output e Trivial Iterator per vederlo tutto).

Quel documento è un’interfaccia perfettamente valida e “i dettagli reali del concetto” sono definiti proprio lì. Non sono definiti dalle implementazioni di Forward Iterators e nemmeno sono definiti dagli algoritmi che usano Forward Iterators.

Le differenze nel modo in cui le interfacce sono gestite tra STL e Java sono triplici:

1) STL definisce espressioni valide usando l’object, mentre Java definisce metodi che devono essere richiamabili sull’object. Naturalmente un’espressione valida potrebbe essere una chiamata metodo (funzione membro), ma non deve essere.

2) Le interfacce Java sono oggetti runtime, mentre i concetti STL non sono visibili in fase di esecuzione anche con RTTI.

3) Se non si riesce a rendere valide le espressioni valide richieste per un concetto STL, si ottiene un errore di compilazione non specificato quando si crea un’istanza di un modello con il tipo. Se non si implementa un metodo richiesto di un’interfaccia Java, si ottiene un errore di compilazione specifico che lo dice.

Questa terza parte è se ti piace una sorta di “duck typing” (in fase di compilazione): le interfacce possono essere implicite. In Java, le interfacce sono piuttosto esplicite: una class “è” Iterable se e solo se dice che implementa Iterable. Il compilatore può verificare che le firme dei suoi metodi siano tutte presenti e corrette, ma la semantica è ancora implicita (cioè sono documentate o meno, ma solo un numero maggiore di codici (test unitari) può dirti se l’implementazione è corretta).

In C ++, come in Python, sia la semantica che la syntax sono implicite, anche se in C ++ (e in Python se si ottiene il preprocessore di tipizzazione forte) si ottiene un aiuto dal compilatore. Se un programmatore richiede una dichiarazione esplicita di interfacce Java simile alla class di implementazione, l’approccio standard consiste nell’utilizzare i tratti di tipo (e l’ereditarietà multipla può impedire che questo sia troppo dettagliato). Quello che manca, rispetto a Java, è un singolo modello che posso istanziare con il mio tipo e che verrà compilato se e solo se tutte le espressioni richieste sono valide per il mio tipo. Questo mi direbbe se ho implementato tutti i bit necessari, “prima di usarlo”. Questa è una comodità, ma non è il nucleo di OOP (e ancora non verifica la semantica, e il codice per testare la semantica dovrebbe naturalmente testare anche la validità delle espressioni in questione).

STL può o non può essere sufficientemente OO per i tuoi gusti, ma certamente separa l’interfaccia in modo pulito dall’implementazione. Manca la capacità di Java di fare riflessioni sulle interfacce e segnala le violazioni dei requisiti di interfaccia in modo diverso.

puoi dire alla funzione … si aspetta un Forward Iterator solo guardando la sua definizione, dove avresti bisogno di guardare l’implementazione o la documentazione per …

Personalmente penso che i tipi impliciti siano una forza, se usati in modo appropriato. L’algoritmo dice quello che fa con i suoi parametri del template, e l’implementatore si assicura che funzioni: è esattamente il denominatore comune di ciò che le “interfacce” dovrebbero fare. Inoltre con STL, è improbabile che tu stia usando, ad esempio, std::copy basato sulla ricerca della sua dichiarazione diretta in un file di intestazione. I programmatori dovrebbero capire cosa richiede una funzione in base alla sua documentazione, non solo sulla firma della funzione. Questo è vero in C ++, Python o Java. Ci sono limitazioni su cosa è ansible ottenere con la digitazione in qualsiasi lingua, e provare a usare la digitazione per fare qualcosa che non fa (controllare la semantica) sarebbe un errore.

Detto questo, gli algoritmi STL di solito nominano i loro parametri del modello in un modo che rende chiaro quale concetto è richiesto. Tuttavia, questo è di fornire utili informazioni supplementari nella prima riga della documentazione, non di rendere le dichiarazioni di inoltro più istruttive. Ci sono più cose che devi sapere che possono essere incapsulate nei tipi dei parametri, quindi devi leggere i documenti. (Ad esempio, in algoritmi che accettano un intervallo di input e un iteratore di output, è probabile che l’iteratore di output abbia bisogno di uno “spazio” sufficiente per un certo numero di output in base alle dimensioni dell’intervallo di input e forse ai valori in esso. )

Ecco Bjarne su interfacce esplicitamente dichiarate: http://www.artima.com/cppsource/cpp0xP.html

Nei generici, un argomento deve essere di una class derivata da un’interfaccia (l’equivalente C ++ all’interfaccia è una class astratta) specificato nella definizione del generico. Ciò significa che tutti i tipi di argomenti generici devono rientrare in una gerarchia. Ciò impone vincoli non necessari sui progetti richiede una previsione irragionevole da parte degli sviluppatori. Ad esempio, se scrivi un generico e definisco una class, le persone non possono usare la mia class come argomento per il tuo generico a meno che non conosca l’interfaccia che hai specificato e abbia derivato la mia class da essa. È rigido

Guardando il contrario, digitando anatra è ansible implementare un’interfaccia senza sapere che l’interfaccia esiste. Oppure qualcuno può scrivere un’interfaccia deliberatamente tale che la tua class lo implementa, dopo aver consultato i tuoi documenti per vedere che non chiedono nulla che tu già non faccia. È flessibile.

“OOP per me significa solo messaggistica, conservazione locale e protezione e occultamento del processo statale, e estremo legame tardivo di tutte le cose. Può essere fatto in Smalltalk e in LISP. Ci sono probabilmente altri sistemi in cui ciò è ansible, ma Non sono a conoscenza di loro. ” – Alan Kay, creatore di Smalltalk.

C ++, Java e la maggior parte delle altre lingue sono abbastanza lontane dal classico OOP. Detto questo, discutere delle ideologie non è terribilmente produttivo. C ++ non è puro in alcun senso, quindi implementa funzionalità che sembrano avere un senso pragmatico al momento.

Il problema di base con

 void MyFunc(ForwardIterator *I); 

è come si ottiene in modo sicuro il tipo di cosa restituisce l’iteratore? Con i modelli, questo è fatto per te al momento della compilazione.

STL è partito con l’intento di fornire una grande libreria che copra l’algoritmo più comunemente utilizzato – con l’objective di comportamento e prestazioni coerenti. Il modello è diventato un fattore chiave per rendere realizzabili l’implementazione e il target.

Solo per fornire un altro riferimento:

Al Stevens interviste Alex Stepanov, nel marzo 1995 di DDJ:

Stepanov ha spiegato la sua esperienza lavorativa e la sua scelta verso una vasta libreria di algoritmi, che alla fine si è evoluta in STL.

Dicci qualcosa sul tuo interesse a lungo termine nella programmazione generica

….. Poi mi è stato offerto un lavoro presso Bell Laboratories lavorando nel gruppo C ++ su librerie C ++. Mi hanno chiesto se potevo farlo in C ++. Certo, non conoscevo il C ++ e, naturalmente, ho detto che potevo. Ma non potevo farlo in C ++, perché nel 1987 il C ++ non aveva template, che sono essenziali per abilitare questo stile di programmazione. L’ereditarietà era l’unico meccanismo per ottenere la genericità e non era sufficiente.

Anche l’ereditarietà del C ++ non è di grande utilità per la programmazione generica. Discutiamo perché. Molte persone hanno tentato di utilizzare l’ereditarietà per implementare strutture dati e classi contenitore. Come sappiamo ora, ci sono stati pochi tentativi riusciti. L’ereditarietà in C ++ e lo stile di programmazione ad esso associato sono drammaticamente limitati. È imansible implementare un progetto che includa qualcosa di banale come l’uguaglianza che lo usa. Se inizi con una class base X nella radice della tua gerarchia e definisci un operatore di uguaglianza virtuale su questa class che accetta un argomento del tipo X, allora deriva la class Y dalla class X. Qual è l’interfaccia dell’uguaglianza? Ha uguaglianza che paragona Y a X. Usando gli animali come esempio (le persone OO amano gli animali), definisci i mammiferi e ricava la giraffa dai mammiferi. Quindi definire un compagno di funzione membro, in cui l’animale si accoppia con l’animale e restituisce un animale. Quindi derivi la giraffa dall’animale e, naturalmente, ha una funzione di compagno in cui la giraffa si accoppia con l’animale e restituisce un animale. Non è sicuramente quello che vuoi. Mentre l’accoppiamento potrebbe non essere molto importante per i programmatori C ++, l’uguaglianza lo è. Non conosco un singolo algoritmo in cui l’uguaglianza di qualche tipo non viene utilizzata.

Per un momento, pensiamo alla libreria standard come a un database di raccolte e algoritmi.

Se hai studiato la storia dei database, sai senza dubbio che, all’inizio, i database erano per lo più “gerarchici”. I database gerarchici corrispondevano molto strettamente all’originale OOP, in particolare la varietà a ereditarietà, come quella usata da Smalltalk.

Col passare del tempo, divenne evidente che i database gerarchici potevano essere usati per modellare quasi tutto, ma in alcuni casi il modello di ereditarietà era piuttosto limitante. Se avessi una porta di legno, è stato utile poterla guardare come una porta, o come un pezzo di una certa materia prima (acciaio, legno, ecc.)

Quindi, hanno inventato i database dei modelli di rete. I database dei modelli di rete corrispondono molto strettamente all’ereditarietà multipla. C ++ supporta completamente l’ereditarietà multipla, mentre Java supporta un modulo limitato (puoi ereditare da una sola class, ma puoi anche implementare tutte le interfacce che vuoi).

Sia il modello gerarchico che i database dei modelli di rete sono per lo più sbiaditi dall’uso generale (anche se alcuni rimangono in nicchie abbastanza specifiche). Per la maggior parte degli scopi, sono stati sostituiti da database relazionali.

Gran parte della ragione per cui i database relazionali hanno preso il sopravvento è stata la versatilità. Il modello relazionale è funzionalmente un superset del modello di rete (che è, a sua volta, un superset del modello gerarchico).

Il C ++ ha seguito in gran parte lo stesso percorso. La corrispondenza tra l’ereditarietà singola e il modello gerarchico e tra l’ereditarietà multipla e il modello di rete sono abbastanza ovvi. La corrispondenza tra i modelli C ++ e il modello gerarchico può essere meno ovvia, ma è comunque abbastanza vicino.

Non ne ho visto una prova formale, ma credo che le capacità dei modelli siano un superset di quelle fornite dall’ereditarietà multipla (che è chiaramente un superset di un’unica inerzia). L’unica parte difficile è che i template sono per lo più legati staticamente – cioè, tutto il binding avviene al momento della compilazione, non il tempo di esecuzione. In quanto tale, una prova formale che l’ereditarietà fornisce un superset delle capacità dell’ereditarietà potrebbe essere alquanto difficile e complessa (o addirittura imansible).

In ogni caso, penso che sia la maggior parte della vera ragione per cui C ++ non usa l’ereditarietà per i suoi contenitori – non c’è un vero motivo per farlo, perché l’ereditarietà fornisce solo un sottoinsieme delle funzionalità fornite dai modelli. Poiché i modelli sono fondamentalmente una necessità in alcuni casi, potrebbero essere utilizzati quasi ovunque.

Come si fanno i confronti con ForwardIterator *? Cioè, come controlli se l’articolo che hai è quello che stai cercando o lo hai superato?

Il più delle volte, vorrei usare qualcosa del genere:

 void MyFunc(ForwardIterator& i) 

il che significa che so che sto puntando a MyType, e so come confrontarli. Anche se sembra un modello, non è realmente (nessuna parola chiave “modello”).

Questa domanda ha molte ottime risposte. Va anche detto che i modelli supportano un design aperto. Con lo stato attuale dei linguaggi di programmazione orientati agli oggetti, è necessario utilizzare lo schema del visitatore quando si affrontano tali problemi e il vero OOP dovrebbe supportare più associazioni dinamiche. Vedi Open Multi-Methods per C ++, P. Pirkelbauer, et.al. per una lettura molto interessante.

Un altro punto interessante di modelli è che possono essere utilizzati anche per il polimorfismo di runtime. Per esempio

 template Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func) { auto dt=(t_end-t_0)/N; for(size_t k=0;k 

Nota che questa funzione funzionerà anche se Value è un vettore di qualche tipo ( non std :: vector, che dovrebbe essere chiamato std::dynamic_array per evitare confusione)

Se func è piccola, questa funzione guadagnerà molto dall'inlining. Esempio di utilizzo

 auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y) {return y;}); 

In questo caso, dovresti conoscere la risposta esatta (2.718 ...), ma è facile build un semplice ODE senza soluzione elementare (Suggerimento: usa un polinomio in y).

Ora, hai una grande espressione in func , e usi il solutore ODE in molti posti, così il tuo eseguibile viene inquinato con istanze di template ovunque. Cosa fare? La prima cosa da notare è che un puntatore a funzioni regolari funziona. Quindi vuoi aggiungere currying in modo da scrivere un'interfaccia e un'istanza esplicita

 class OdeFunction { public: virtual double operator()(double t,double y) const=0; }; template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); 

Ma l'istanziazione di cui sopra funziona solo per il double , perché non scrivere l'interfaccia come modello:

 template class OdeFunction { public: virtual Value operator()(double t,const Value& y) const=0; }; 

e specializzati per alcuni tipi di valori comuni:

 template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); template vec4_t euler_fwd(size_t N,double t_0,double t_end,vec4_t y_0,const OdeFunction< vec4_t >& func); // (Native AVX vector with four components) template vec8_t euler_fwd(size_t N,double t_0,double t_end,vec8_t y_0,const OdeFunction< vec8_t >& func); // (Native AVX vector with 8 components) template Vector euler_fwd(size_t N,double t_0,double t_end,Vector y_0,const OdeFunction< Vector >& func); // (A N-dimensional real vector, *not* `std::vector`, see above) 

Se la funzione fosse stata progettata prima su un'interfaccia, allora sarebbe stata forzata ad ereditare da quella ABC. Ora hai questa opzione, così come il puntatore a funzione, lambda, o qualsiasi altro object funzione. La chiave qui è che dobbiamo avere operator()() , e dobbiamo essere in grado di utilizzare alcuni operatori aritmetici sul suo tipo di ritorno. Thus, the template machinery would break in this case if C++ did not have operator overloading.

The concept of separating interface from interface and being able to swap out the implementations is not intrinsic to Object-Oriented Programming. I believe it’s an idea that was hatched in Component-Based Development like Microsoft COM. (See my answer on What is Component-Driven Development?) Growing up and learning C++, people were hyped out inheritance and polymorphism. It wasn’t until 90s people started to say “Program to an ‘interface’, not an ‘implementation'” and “Favor ‘object composition’ over ‘class inheritance’.” (both of which quoted from GoF by the way).

Then Java came along with built-in garbage collector and interface keyword, and all of a sudden it became practical to actually separate interface and implementation. Before you know it the idea became part of the OO. C++, templates, and STL predates all of this.