2011-11-16 18 views
16

che sto cercando di fare una semplice classe Vector (matematica) in questo modo:: come controllare il numero di argomenti del costruttore utilizzando la variabile del modello.

template <int D, typename T = float> 
class Vector 
{ 
    T m[D]; 
    // ... 
}; 

Dove D è il numero di dimensioni. Se è due, il vettore memorizzerà due valori di tipo T.


Come posso dichiarare la funzione di costruzione di prendere D argomenti di tipo T?

Vector<2> v(1.0f, -6.3f); 

Come aggiungere una funzione solo se D se un numero specifico? Vorrei aggiungere GetX() se D è> = 1, GetY() se D è> = 2 e GetZ() se D è> = 3, ma il seguente codice dovrebbe generare un errore di compilazione:

Vector<2> v(1.0f, -6.3f); 
cout << v.GetZ() << endl; 

come generare un errore in fase di compilazione se D è < 1?

Non sto seguendo uno standard specifico, tutto funzionerà per me.

+1

È possibile utilizzare i modelli varadic – Dani

+1

non '' Vector <-1> senso? :) Vorrei usare 'std :: size_t' invece di' int'. – GManNickG

+2

@GMan 'Vector <0>' anche non ha senso. Comunque, 'size_t' è migliore. –

risposta

13

non ho accesso a un compilatore C++ 11 ma forse qualcosa del genere potrebbe funzionare?

#include <array> 
#include <type_traits> 

template <int D, typename T> 
class Vector 
{ 
    static_assert(D > 0, "Dimension must be greater than 0"); 
    std::array<T,D> m; 
public: 
    template<typename... Args> 
    Vector(Args&&... args) : m{T(args)...} 
    { 
     static_assert(sizeof...(Args) == D, "Invalid number of constructor arguments."); 
    } 

    T GetX() const { return m[0]; } 
    T GetY() const { return m[1]; } 
    T GetZ() const { return m[2]; } 
}; 

template <typename T> 
class Vector<1, T> 
{ 
    std::array<T,1> m; 
public: 
    Vector(const T& t0) : m{t0} 
    { 
    } 

    T GetX() const { return m[0]; } 
}; 

template <typename T> 
class Vector<2, T> 
{ 
    std::array<T,2> m; 
public: 
    Vector(const T& t0, const T& t1) : m{t0, t1} 
    { 
    } 

    T GetX() const { return m[0]; } 
    T GetY() const { return m[1]; } 
}; 
+1

È necessario aggiungere alcuni' static_assert's alle funzioni 'Get'. –

+0

Non riesco a farlo funzionare su GCC 4.7. Il compilatore non sta selezionando il costruttore con una chiamata a 'Vector <4, double> :: Vector (double, double, double, double)'. (e c'era un 'typename' mancante che ho corretto.) –

+1

Questo dovrebbe essere davvero' std :: forward (args) 'nel costruttore – Puppy

5

Questo è un po 'fuori tema, ma forse sarà la minima quantità di lavoro: utilizzare un tuple. Quindi ottieni tutte le funzioni di accesso gratuitamente.

L'unica cosa che resta da fare è quello di rendere una fabbrica di tuple:

template <typename T, unsigned int D> struct tuple_maker 
{ 
    typedef typename tcat<T, tuple_maker<T, D - 1>::type>::type type; 
} 
template <typename T> struct tuple_maker<T, 1> 
{ 
    typedef std::tuple<T> type; 
} 

abbiamo bisogno degli ausiliari tcat:

template <typename T, typename Tuple> struct tcat; 
template <typename T, typename ...Args> struct tcat<T, std::tuple<Args...>> 
{ 
    typedef typename std::tuple<T, Args...> type; 
} 

Usage:

tuple_maker<float, 3>::type myVec3D; 

con gli alias di template siamo può fare uno migliore:

template <typename T, unsigned int D> 
using MyVector = typename tuple_maker<T, D>::type; 

MyVector<double, 4> myVec4D; 
+0

Se si eredita dalla tupla, si può semplicemente ereditare il suo costruttore (beh, quando i compilatori lo supportano)! –

+0

Bene, questo funziona. Ma sono ancora curioso di sapere come farlo "a mano". Inoltre, in questo modo, non posso aggiungere ulteriori metodi. –

+0

"A mano" sarebbe essenzialmente la stessa cosa in cui è implementato 'tuple' ... in alternativa, si potrebbe andare per' array '(come si fa già, in sostanza) e aggiungere un costruttore con un inizializzatore di controvento elenco. Non otterrete la libera circolazione o la costruzione sul posto in quel modo, ma forse è OK per i tipi di float. –

1

How can I declare the constructor function to take D arguments of type T?

Non si può fare questo. È possibile specializzarsi per ciascuna delle dimensioni supportate e fornire il costruttore appropriato per ciascuna di esse. Oppure puoi definire un costruttore che accetta più argomenti con valori predefiniti e ignora quelli che non vengono utilizzati. Oppure puoi definire più costruttori, da 1 a un limite superiore, e static_assert se il numero di argomenti è maggiore di D.

How to add a function only if D if a specific number?

che si fa con la specializzazione. Dovresti spostare tutte le funzionalità comuni in alcuni template di VectorBase ed ereditarne, e fare una specializzazione parziale sulla dimensionalità per aggiungere quelle funzioni.

How to generate a compile-time error if D is < 1?

Oppure si potrebbe definire tutte quelle funzioni per il modello di base, e se static_assertD non è sufficiente per essere utilizzando tale funzione. Ora però hai appena perso l'istanza esplicita. È anche possibile aggiungere un parametro dummy template a tali funzioni, in modo da poter utilizzare enable_if e SFINAE per scartare le funzioni.

+0

@ R.MartinhoFernandes: O forse ereditarietà più le chiamate al costruttore di inoltro ... –

+0

"Come posso dichiarare la funzione di costruzione per prendere gli argomenti D di tipo T? Non puoi farlo" Non è vero ... lui può usa i modelli varadici – Dani

+0

@Kerrek SB: buon commento, posto sbagliato –

16

Quindi ho fornito una risposta un po 'stupida che piaceva alla gente. Ma questo è molto più facile di quello :)

template <int D, typename T = float> 
class v { 
public: 
    template <typename... Args> 
    v(Args... args) : a{ T(args)... } { 
     static_assert(sizeof...(Args) == D, "wrong number of arguments"); 
    } 

private: 
    T a[D]; 
}; 

È possibile utilizzare i modelli variadic e SFINAE per ottenere un costruttore con il giusto numero di parametri.

I costruttori non hanno valori di ritorno, quindi è necessario utilizzare SFINAE su uno dei parametri. E per usare i modelli variadici, avremo bisogno di avere il pacchetto di parametri alla fine.

Ciò significa che è necessario utilizzare SFINAE sul primo parametro.

Quindi questo significa che il pacchetto di parametri dopo il primo parametro deve avere un parametro in meno rispetto alle dimensioni.

Con questo in mano, possiamo scrivere:

template <int D, typename T> 
class v { 
public: 
    template <typename... Tail> 
    v(typename std::enable_if<sizeof...(Tail)+1 == D, T>::type head, Tail... tail) 
    : a{ head, T(tail)... } {} 

private: 
    T a[D]; 
}; 

E ora:

v<4, int> a(1,2,3,4); // ok! 
v<4, int> b(1,2,3); // error! no such constructor 
+1

Molto bella! Questa dovrebbe essere la risposta. –

+0

Non '' il modello 'sarà migliore? – Xeo

+0

@Xeo No, ciò renderebbe un pacchetto di parametri 'float'. Non un pacchetto di 'typename's in cui tutti i tipi sono' float's. –

2

Questo dovrebbe fare il lavoro:

template<int N, typename T> 
class Array 
{ 
private: 
    T Data[N]; 

public: 
    template<int i> 
    void Init() { } 
    template<int i, typename... Args> 
    void Init(T Arg0, Args... Rest) 
    { 
     Data[i] = Arg0; 
     Init<i + 1>(Rest...); 
    } 
    template<typename... Args> 
    Array(T Arg0, Args... Rest) 
    { 
     static_assert(sizeof...(Args) + 1 == 5, "Wrong number of arguments"); 
     Data[0] = Arg0; 
     Init<1>(Rest...); 
    } 
}; 

int main (int argc, const char * argv[]) 
{ 
    Array<5, int> arr(1, 2, 3, 4, 5); 
    return 0; 
} 
Problemi correlati