Ich habe folgendes Problem. Ich definiere einen N-dimensionalen Vektor als solchen
#include <vector>
#include <utility>
#include <string>
template <int N, typename T>
struct NVector{
typedef std::vector<typename NVector<N-1,T>::type> type;
};
template <typename T> struct NVector<1,T> {
typedef std::vector<T> type;
};
Ich möchte eine Funktion Map höherer Ordnung schreiben , die die Blattelemente des verschachtelten Vektors transformieren kann, egal wie tief sie sind, und einen neuen verschachtelten Vektor derselben Form zurückgeben kann. Ich habe versucht
template <int N, typename T, typename Mapper>
struct MapResult {
typedef decltype ( (std::declval<Mapper>()) (std::declval<T>()) ) basic_type;
typedef typename NVector<N, basic_type>::type vector_type;
};
template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
{
typename MapResult<N,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(Map(*i,mapper));
}
return out;
}
template <typename T, typename Mapper>
typename MapResult<1,T,Mapper>::vector_type
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
{
typename MapResult<1,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(mapper(*i));
}
return out;
}
und dann in der Hauptsache wie verwenden
int main(){
NVector<1,int>::type a = {1,2,3,4};
NVector<2,int>::type b = {{1,2},{3,4}};
NVector<1,std::string>::type as = Map(a,[](int x){return std::to_string(x);});
NVector<2,std::string>::type bs = Map(b,[](int x){return std::to_string(x);});
}
Ich bekomme jedoch Kompilierungsfehler
<source>:48:34: error: no matching function for call to 'Map'
NVector<1,double>::type da = Map(a,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
<source>:49:34: error: no matching function for call to 'Map'
NVector<2,double>::type db = Map(b,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
2 errors generated.
Compiler returned: 1
Ich vermute, dass der Compiler nicht klug genug ist (oder der Standard nicht angibt, wie), um den Parameter N durch Abzug herauszufinden. Gibt es eine Möglichkeit, dies zu erreichen?
Früher funktionierte dies, aber auf eine andere Art und Weise, indem ich es tatsächlich von std :: vector ableitete, aber ich mag diese Lösung nicht, da es schön wäre, wenn sie mit aktuell vorhandenem Code funktioniert, ohne einen neuen Wrapper-Typ einführen zu müssen.
/// define recursive case
template <int N, typename T>
struct NVector : std::vector<NVector<N-1,T>>;
/// define termination case
template <typename T>
struct NVector<1, T> : public std::vector<T>;
Live-Code unter https://godbolt.org/z/AMxpuj
quelle
T
als Argument verwenden und verwendenenable_if
Antworten:
Sie können nicht aus einem typedef ableiten - insbesondere nicht aus einem typedef, das in einer Hilfsklasse deklariert ist -, da der Compiler die umgekehrte Zuordnung von einem Typ zu Kombinationen von Argumenten nicht durchführen kann.
(Bedenken Sie, dass dies im allgemeinen Fall unmöglich ist, da sich jemand spezialisieren könnte
struct NVector<100, float> { using type = std::vector<char>; };
und der Compiler nicht wissen kann, ob dies beabsichtigt ist.)Um dem Compiler zu helfen, können Sie die umgekehrte Zuordnung definieren:
Mögliche Verwendung (C ++ 17, aber die Übersetzung in archaische Dialekte ist einfach genug ):
quelle
N==0
und andereWie bereits in anderen Antworten erwähnt, besteht das Problem hier darin, dass der verschachtelte Namensbezeichner in einer qualifizierten ID ein nicht abgeleiteter Kontext ist [temp.deduct.type] /5.1 . Andere Antworten haben bereits zahlreiche verschiedene Möglichkeiten aufgezeigt, wie Sie Ihren ursprünglichen Ansatz umsetzen können. Ich möchte einen Schritt zurücktreten und überlegen, was Sie eigentlich tun möchten.
Alle Ihre Probleme ergeben sich aus der Tatsache, dass Sie versuchen, in Bezug auf die Hilfsvorlage zu arbeiten
NVector
. Der einzige Zweck dieser Hilfsvorlage scheint darin zu bestehen, eine Spezialisierung von verschachtelten zu berechnenstd::vector
. Der einzige Zweck der HilfsvorlageMapResult
scheint darin zu bestehen, die Spezialisierung von verschachtelten Funktionen zu berechnenstd::vector
, die erforderlich ist, um das Ergebnis der Anwendung Ihrer beliebigenmapper
Funktion auf jedes Element der verschachtelten Eingabevektorstruktur zu erfassen . Nichts zwingt Sie dazu, IhreMap
Funktionsvorlage in Bezug auf diese Hilfsvorlagen auszudrücken . In der Tat ist das Leben viel einfacher, wenn wir sie einfach loswerden. Sie wollten eigentlich nur eine beliebigemapper
Funktion auf jedes Element einer verschachteltenstd::vector
Struktur anwenden . Also machen wir das einfach:Arbeitsbeispiel hier
Löschen Sie einfach die nachfolgenden Rückgabetypen, wenn Sie C ++ 14 oder höher verwenden können.
Wenn Sie tatsächlich nur ein nD- Array speichern und bearbeiten möchten , sollten Sie berücksichtigen, dass eine verschachtelte Struktur
std::vector
nicht unbedingt die effizienteste Methode ist. Sofern nicht jeder Untervektor möglicherweise eine andere Größe haben muss, gibt es keinen Grund dafür, dass die Anzahl der von Ihnen durchgeführten dynamischen Speicherzuordnungen mit der Anzahl der Dimensionen exponentiell zunimmt und Sie mit dem Zeiger auf jedes Element zugreifen. Verwenden nur einerstd::vector
alle Elemente der n zu halten D - Array und eine Zuordnung zwischen logischen n definieren D - Element - Indizes und 1D linearen Speicherindex, beispielsweise in einer Weise , ähnlich dem , was in vorgeschlagen wurde diese Antwort. Dies ist nicht nur effizienter als das Verschachteln von Vektoren, sondern ermöglicht Ihnen auch das einfache Ändern des Speicherlayouts, in dem Ihre Daten gespeichert sind. Da der zugrunde liegende Speicher ein einfaches lineares Array ist, kann die Iteration aller Elemente mit nur einer einfachen Schleife durchgeführt werden. Die Antwort auf Ihre Frage, einen Bereich von Elementen einem anderen zuzuordnen, lautet einfachstd::transform
…quelle
std::vectors
. Der obige Ansatz macht genau das und funktioniert für jedes N!? Es gibt zwei Überladungen, eine, die dem Fall eines Vektors entspricht, der noch einen weiteren Vektor enthält und zu einer Rekursion nach unten führt, und eine, die den Basisfall behandelt, in dem die Rekursion stoppt…Sie brauchen nicht
NVector
zu definieren ,MapResult
undMap
.quelle
Im Allgemeinen
typename NVector<N,T>::type
können Sie nicht ableiten,N,T
da es viele Instanzen einer Vorlage geben kann, die denselben verschachtelten Typ erzeugen.Ich weiß, dass Sie ein 1: 1-Mapping geschrieben haben, aber die Sprache erfordert es nicht. Daher gibt es keine Unterstützung dafür, auf diese Weise rückwärts zu arbeiten. Immerhin hast du geschrieben
typename NVector<N,T>::type
, aber was du tatsächlich passierst, iststd::vector<std::vector<int>>
oder was auch immer. Es gibt keinen allgemeinen Weg, es zurückzuziehen.Die einfache Lösung besteht darin, NVector als Werttyp zu verwenden und nicht nur Vektortypedefs zu erstellen.
Ändern Sie dann Map und MapResult so, dass sie direkt in Bezug auf funktionieren
NVector<N,T>
, wodurch der Typabzug wie gewohnt möglich ist. Zum Beispiel wird die allgemeine KarteSchließlich müssen Sie Ihre lokalen Variablen als
NVector<1,int>
no deklarieren::type
, und leider werden die Initialisierer etwas hässlicher, da Sie{}
jede Ebene zusätzlich umschließen müssen . Sie können jedoch jederzeit einen Konstruktor schreibenNVector
, um dies zu umgehen.Oh, und erwägen Sie,
std::transform
diese Schleife zu verwenden, anstatt sie von Hand zu schreiben.quelle
T
, vom Typ zu seinstd::vector
.Sie können eine Teilspezialisierung verwenden, um sozusagen N rückwärts abzuleiten.
Dies könnte mit SFINAE verwendet werden, um so etwas zu tun
quelle
Es ist völlig richtig, dass der Compiler nicht versucht zu erraten, was Sie meinen, weil es mehrdeutig ist. Möchten Sie die Funktion mit
NVector<2, int>
oder aufrufenNVector<1, std::vector<int>>
? Beide sind absolut gültig und beide würden Ihnen das gleichetype
Mitglied typedef geben.Ihre vorherige Lösung hat funktioniert, da Sie wahrscheinlich den Vektor in diesem Typ übergeben haben (das Argument hatte also den Typ
NVector<2, int>
und von dort ist es einfach, die richtigen Vorlagenparameter abzuleiten). Sie haben meiner Meinung nach drei Möglichkeiten:std::vector
erneut in Ihren benutzerdefinierten Typ ein. Aber ich würde das nicht mit Vererbung tun, sondern nur mit einem Mitglied und impliziter Konvertierung in den Typ dieses Mitglieds.Nvector<N,T>
würde dies tun), der den Aufruf eindeutig macht.Ich denke, der dritte ist der einfachste und klarste.
quelle
T
undN
sind nicht ableitbar in:Stattdessen könnten Sie Folgendes tun:
Demo
quelle