8

Also, was ich will, ist multidimensionalen Vektor bestimmter Typ zu schaffen, wo die erste Dimension Größe des ersten Arguments eines Funktionsaufruf hat, etc, zum Beispiel, wenn ichErstellen n-dimensionalen Vektor mit bestimmten Größen

tun
std::size_t n = 5; 
auto x = make_vector<int>(n + 1, n * 2, n * 3); 

x sollte 6x10x15 3D-Array sein (von Nullen aus, weil ich jetzt auf dem Standard konstruieren will)

ich habe versucht, dies:

template <typename T> 
std::vector<T> make_vector(std::size_t size) { 
    return std::vector<T>(size); 
} 

template <typename T, typename... Args> 
auto make_vector(std::size_t first, Args... sizes) -> std::vector<decltype(make_vector<T>(sizes...))> { 
    auto inner = make_vector<T>(sizes...); 
    return std::vector<decltype(inner)>(first, inner); 
} 

Es scheint, für 1 oder 2 Argumente zu arbeiten, aber für 3 Argumente wird mit Fehler folgenden (Klirren ++)

In file included from /Users/riad/ClionProjects/for-jhelper/output/run.cpp:1: 
/Users/riad/ClionProjects/for-jhelper/tasks/TaskC.cpp:12:12: error: no matching function for call to 'make_vector' 
       auto x = make_vector<int>(n + 1, n * 2, n * 3); 
         ^~~~~~~~~~~~~~~~ 
/Users/riad/ClionProjects/for-jhelper/tasks/../spcppl/make_vector.hpp:9:6: note: candidate template ignored: substitution failure [with T = int, Args = <unsigned long, unsigned long>]: call to function 'make_vector' that is neither visible in the template definition nor found by argument-dependent lookup 
auto make_vector(std::size_t first, Args... sizes) -> std::vector<decltype(make_vector<T>(sizes...))> { 
    ^                 ~~~~~~~~~~~ 
/Users/riad/ClionProjects/for-jhelper/tasks/../spcppl/make_vector.hpp:4:16: note: candidate function template not viable: requires single argument 'size', but 3 arguments were provided 
std::vector<T> make_vector(std::size_t size) { 

Wenn ich richtig Problem zu verstehen ist, dass, wenn Compiler versucht Rückgabewert von make_vector berechnen sie das wissen müssen Gibt den Wert des Vektors mit einer geringeren Anzahl von Argumenten zurück und schlägt dies fehl. Wie repariere ich das?

+0

@Columbo, könnten Sie elaborate? – RiaD

+0

Sind Sie ernsthaft ernsthaft in Erwägung, 'vector >'? Wenn Sie mehrdimensionale Arrays benötigen, verwenden Sie eine eindimensionale und wickeln Sie sie ein. – Columbo

+0

@Columbo was für? – RiaD

Antwort

4

einen Namespace erstellen schätzen einige Helfer in sie zu setzen, die so genannte details.

In details erstellen Typen struct adl_helper{};

eine Implementierung von make_vector erstellen, mit der Ausnahme erste Parameter immer ein Template-Parameter Adl genannt wird. Dieser Vorlagenparameter wird niemals benannt, und Instanzen davon werden an Rekursionen übergeben.

Implementieren make_vector(blah) durch Aufruf details::make_vector<T>(details::adl_helper{}, blah).

namespace details { 
    struct adl_helper { }; 

    template <class T, class Adl> 
    std::vector<T> make_vector(Adl, size_t size) { 
    return std::vector<T>(size); 
    } 

    template <class T, class Adl, class... Args, 
    class R_T=decltype(
     make_vector<T>(Adl{}, std::declval<Args>()...) 
    ), 
    class R=std::vector<R_T> 
    > 
    R make_vector(Adl, size_t first, Args... sizes) 
    { 
    auto inner = make_vector<T>(Adl{}, std::forward<Args>(sizes)...); 
    return R(first, inner); 
    } 
} 


template <class T, class... Args, 
    class R=decltype(
    details::make_vector<T>(details::adl_helper{}, std::declval<Args>()...) 
) 
> 
R make_vector(Args... args) 
{ 
    return details::make_vector<T>(details::adl_helper{}, std::forward<Args>(args)...); 
} 

Was hier vor sich geht, ist, dass ein scheinbar rekursiven Aufruf in der Unterzeichnung einer Template-Funktion in zwei Kontexten ausgewertet wird.

Zuerst wird ausgewertet, wo es deklariert ist. Insbesondere wird ausgewertet, bevor definiert wurde. Es "fängt" sich also nicht.

Zweitens, ein ADL (Argument abhängige Lookup) basierte Pass erfolgt an dem Punkt, wo es instanziiert nur basierend auf Vorlagen Typen an die Funktion übergeben wird.

Die Typen template<class Adl> und adl_helper bedeutet, dass diese argumentabhängige Suche die details::make_vector-Funktion selbst anzeigen kann, wenn sie instanziiert wird. Und wenn es den Rückgabetyp nachschlägt, kann es auch siehe details::make_vector. Usw.

live example.

Die Verwendung von class R= Aliase und dergleichen ist nur da, um den Code zu bereinigen und einige unnötige Duplizierung zu reduzieren.

In diesem speziellen Fall ist die Anstrengung, die erforderlich ist, um den Rückgabetyp zu bauen, einfacher als all diese Gymnastik.

Ich denke, this solution is cleaner:

template<class T>struct tag{using type=T;}; 
template<class Tag>using type=typename Tag::type; 

template<class T, size_t n> 
struct n_dim_vec:tag< std::vector< type< n_dim_vec<T, n-1> > > > {}; 
template<class T> 
struct n_dim_vec<T, 0>:tag<T>{}; 
template<class T, size_t n> 
using n_dim_vec_t = type<n_dim_vec<T,n>>; 
+0

Können Sie näher erläutern, warum 'Bob' sich rekursiv finden kann? – Barry

+0

@barrey du meinst beschreiben, was ADL ist? Oder wie ändert es Template-Lookup-Regeln? Oh, ich habe einen Fehler gemacht, sek. Ok, behoben, denke ich. Ich sollte testen und fügen Sie ein Beispiel, aber helfen, eine Bday-Party für eine 2 Jahre laufen. ;) – Yakk

+0

Wie ändert es die Lookup-Regeln, so dass die Funktionsvorlage 'bob' im Trailing-Return-Typ gefunden werden kann. Ich habe das implementiert und es funktioniert, ich verstehe einfach nicht wie. – Barry

4

Ich schaffe es, dies zu tun, indem ich den Typ separat berechnet, aber das scheint unnötig schwer, obwohl es ziemlich kurz ist.

template <typename T, int n> 
struct NDVector { 
    typedef std::vector<typename NDVector<T, n - 1>::type> type; 
}; 

template <typename T> 
struct NDVector<T, 0> { 
    typedef T type; 
}; 

template <typename T> 
std::vector<T> make_vector(std::size_t size) { 
    return std::vector<T>(size); 
} 


template <typename T, typename... Args> 
typename NDVector<T, sizeof...(Args) + 1>::type make_vector(std::size_t first, Args... sizes) { 
    typedef typename NDVector<T, sizeof...(Args) + 1>::type Result; 
    return Result(first, make_vector<T>(sizes...)); 
} 

wirklich elegantere Lösung

+0

Die Implementierung des nicht spezialisierten 'make_vector' könnte einfach' return {first, make_vector (größen ...)} 'sein. – Lingxi

7

Interessante Frage! Das Problem, auf das Sie stoßen, ist, dass die Suche nach unqualifizierten Namen in den Bereichen, in denen sie verwendet wird, aussieht (in aufsteigender Reihenfolge der Allgemeinheit). Aber von [basic.scope.pdecl]:

Der Punkt der Erklärung für einen Namen ist unmittelbar nach seiner vollständigen declarator (Ziffer 8) und vor seinem initializer (falls vorhanden)

und in dieser Funktion:

template <typename T, typename... Args> 
auto make_vector(std::size_t first, Args... sizes) 
-> std::vector<decltype(make_vector<T>(sizes...))> { 
    ... 
} 

Der "vollständige declarator" schließt den Hinter-Return-Typen. So wird die Funktionsvorlage make_vector<T, Args...> noch nicht in den Geltungsbereich der {. Deshalb wird dies nicht für 3+ Argumente kompilieren.

Die einfachste Lösung wäre, wenn Sie Zugriff auf C++ 14 hätten, würden Sie einfach nicht den nachfolgenden Rückgabetyp benötigen;

template <typename T, typename... Args> 
auto make_vector(std::size_t first, Args... sizes) 
{ /* exactly as before */ } 

Innerhalb des Rumpfes der Namensfunktion können Sie den rekursiven Aufruf ohne Probleme vornehmen.

ohne C++ 14, können Sie es zu einer Klasse-Vorlage weiter, dessen Name im Rahmen aller rekursiven Anrufe werden:

template <typename T, size_t N> 
struct MakeVector 
{ 
    template <typename... Args> 
    static auto make_vector(std::size_t first, Args... sizes) 
    -> std::vector<decltype(MakeVector<T, N-1>::make_vector(sizes...))> 
    { 
     auto inner = MakeVector<T, N-1>::make_vector(sizes...); 
     return std::vector<decltype(inner)>(first, inner);   
    } 
}; 

template <typename T> 
struct MakeVector<T, 1> 
{ 
    static std::vector<T> make_vector(std::size_t size) { 
     return std::vector<T>(size); 
    } 
}; 

template <typename T, typename... Args> 
auto make_vector(Args... args) 
-> decltype(MakeVector<T, sizeof...(Args)>::make_vector(args...)) 
{ 
    return MakeVector<T, sizeof...(Args)>::make_vector(args...); 
} 
1

Ich war nicht bewusst über die anderen Antworten, wenn ich gepostet. Nicht löschen, falls es für jemanden nützlich sein könnte. Natürlich ist die Lösung mit C++ 14 aktiviert.

Mit [unten code] ([email protected] können Sie erreichen:

size_t n = 5; 
    auto x = make_vector<int>(n+1, n*2, n*3); 

Hier ist der Code mit einem minimalen Kommentare:

// Creating a multidimensional container (e.g. `vector`, `map`) 
template<template<typename...> class Container, 
     typename T, 
     size_t DIMENSION> 
struct MultiDimensional 
{ 
    using internal = MultiDimensional<Container, T, DIMENSION-1>; 
    using type = Container<typename internal::type>; 

    template<typename... Args> 
    static 
    type Generate (const size_t size, Args... sizes) 
    { 
    return type(size, internal::Generate(sizes...)); 
    } 
}; 

// Last dimension overload 
template<template<typename...> class Container, 
     typename T> 
struct MultiDimensional<Container, T, 1> 
{ 
    using internal = T; 
    using type = Container<T>; 

    static 
    type Generate (const size_t size) 
    { 
    return type(size); 
    } 
}; 

// Wrapper for the specific requirement of creating a multidimensional `std::vector` 
template<typename T, 
     typename... Args> 
auto make_vector(Args... sizes) 
-> typename MultiDimensional<std::vector, T, sizeof...(sizes)>::type 
{ 
    return MultiDimensional<std::vector, T, sizeof...(sizes)>::Generate(sizes...); 
} 
-2

Die einfachste Lösung ist, um C zu fallen zurück:

void foo(size_t n) { 
    int (*threeDArray)[2*n][3*n] = malloc((n + 1)*sizeof(*threeDArray)); 

    //Do with your array whatever you like. 
    //Here I just initialize it to zeros: 
    for(size_t i = 0; i < n + 1; i++) { 
     for(size_t j = 0; j < 2*n; j++) { 
      for(size_t k = 0; k < 3*n; k++) { 
       threeDArray[i][j][k] = 0; 
      } 
     } 
    } 

    free(threeDArray); 
} 

Wie gesagt, dies ist in C++ nicht möglich: Der C++ - Standard erfordert alle Array-Größen Kompilierzeitkonstanten sein. C ist in dieser Hinsicht viel flexibler, was erlaubt, dass die Array-Laufzeitgrößen überall seit C99, sogar innerhalb von typedefs, liegen.

Also, wenn ich etwas Code habe, der ernsthafte Arbeit mit mehrdimensionalen Arrays zu tun hat, frage ich mich ernsthaft, ob es sich lohnt, diesen Code in eine reine .c-Datei zu verschieben und einfach mit dem Rest meines Projekts zu verknüpfen .

+0

@ Barry Nein, das kannst du nicht. Afaik, selbst der VLA-Vorschlag für C++ 17 wird nur Arrays zulassen, die als lokale Variablen * deklariert sind, wobei die äußere Dimension nur * flexibel ist. Es wird immer noch keine Möglichkeit geben, mit Variablengrößen in Typen umzugehen, wie zum Beispiel in 'threeDArray = (int (*) [2 * n] [3 * n]) malloc (...); Wenn Ihr Compiler mehr VLA-Unterstützung hat, dann ist das, weil es eine Compiler-Erweiterung ist, nicht weil es C++ ist. – cmaster