2015-10-07 7 views
9

Sto usando std::array<size_t, N> (N è una variabile di modello fissa).C'è un modo per far rispettare l'inizializzazione completa di std :: array

#include<array> 
template<size_t N> 
struct A{ 
    size_t function(std::array<size_t, N> arr){ return arr[N-1];} // just an example 
}; 

int main(){ 
    A<5> a; 
    a.function({{1,2,3,4,5}})); 
} 

E funziona correttamente. Il problema è che questo altro codice è in silenzio consentito:

A.function({{1,2,3}})); 

Cioè, anche con elementi perse la array viene inizializzato in qualche modo, anche se è ben definita (elementi ad esempio rimanenti inizializzato a zero, non sono sicuro) questa è una possibile fonte di errori.

C'è un modo per far rispettare l'inizializzazione degli elementi aggiuntivi? Es. generando un errore del compilatore o un avvertimento.

Una possibilità che ho contemplato è quello di utilizzare initializer_list

size_t function2(std::initializer_list<size_t> il){ assert(il.size() == N); ...} 

Il problema è che questo ha generato un errore di runtime al meglio e un controllo in ogni chiamata. Preferirei un errore/avvertimento del compilatore.

Non sono così preoccupato dall'inizializzazione predefinita di std::array<>{} ma dall'inizializzazione incompleta . (Forse non c'è niente che si può fare a questo proposito, dal momento che questo è ereditato dal comportamento di T[N] array statico.)

Ho provato ad utilizzare clang 3.5 e gcc 5.

+0

@texasbruce, grazie, l'ho provato. Il problema è che 'N' in' std :: array 'non può essere dedotto dall'argomento (almeno nel mio compilatore), e da lì in poi' std :: enable_if' non aiuta. O intendi qualcosa di diverso? – alfC

+0

Ho erroneamente interpretato la tua domanda. enable_if non aiuta qui – texasbruce

risposta

2

Risposta semplice: non è possibile.

Quando si inizializza std::array con una lista, si sta facendo un aggregate initialization, e si spiega here quando la dimensione elenco è inferiore al numero di Stati:

  • Se il numero di clausole di inizializzazione è inferiore al il numero di membri o l'elenco di inizializzatori è completamente vuoto, i membri rimanenti vengono inizializzati dai loro inizializzatori brace-or-equal, se forniti nella definizione della classe, e in caso contrario (dal C++ 14) da liste vuote, che eseguono l'inizializzazione del valore. Se un membro di un tipo di riferimento è uno di questi membri rimanenti, il programma è mal formato (i riferimenti non possono essere inizializzati in base al valore)

È semplicemente un comportamento legale e accettabile fornire meno della dimensione, quindi il compilatore non si lamenterà di nulla. Il tuo codice:

A<5> a; 
a.function({{1,2,3}})); 

è equivalente a:

A<5> a; 
a.function({{1,2,3,0,0}})); 

al compilatore. La soluzione migliore è l'errore di runtime (che potrebbe non essere come desiderato).

+0

Suppongo che anche un controllo di runtime non sia possibile perché come si può distinguere uno zero reale da uno zero omesso, ad esempio. Il passaggio all'elenco di inizializzazione è un altro problema. – alfC

+0

Ho accettato questa risposta perché è formalmente corretta, le altre risposte sono buone soluzioni alternative. – alfC

3

È possibile creare un wrapper per gli oggetti

template <class T> 
struct WrapT 
{ 
    WrapT() = delete; 

    WrapT(T e) : value(e){} 

    public: T value; 
    // or add some operator() 
}; 

e

size_t function(std::array<WrapT<size_t>, N> arr){ return arr[N-1].value;} 

così una chiamata di funzione simile (con piena brace-init)

function({ {{1}, {2}, {3}, {4}} }); 

non compilerà perché di uso della funzione cancellata. Live example. La sintassi è un po 'maldestra, e anche allora non sono sicuro che tutti i possibili casi di inizializzazione del valore siano coperti.

@dpy indica che è possibile omettere quelli più interni, in modo da ottenere il codice originale: function({{ 1, 2, 3, 4, 5 }}).

+0

Questo mi ha fatto capire che il problema non è solo di 'std :: array', ma anche del tipo di elemento. Questa risposta è molto concettuale perché mi dice che forse dovrei usare un tipo esplicitamente initalizzato. In "clang" non ho bisogno della parentesi extra attorno ad ogni elemento per qualche motivo. – alfC

+0

WrapT può avere l'operatore di conversione automatica T &() 'e l'operatore T const &() const'. Inoltre posso definire 'template usando initialized_array = std :: array , N>;' e lo uso come '... function (initialized_array arr) {...}'. – alfC

+2

Puoi usare brace-elision, questo ripristina la sintassi '{{1, 2, 3, 4, 5}}' dell'OP. Il primo '{' inizializza 'std :: array', il secondo inizializza l'array interno in stile C di' std :: array' (X), quindi abbiamo un '1' che non è un' {' si applica la brace-elision. I membri di (X), cioè gli elementi 'WrapT ' sono inizializzati da un singolo inizializzatore '1',' 2' e così via. Questo funziona perché 'WrapT (T e)' consente conversioni implicite. – dyp

3

È possibile applicare questo utilizzando il parametro confezione, ma con una sintassi diversa bit:

#include <array> 

template<size_t N> 
struct A{ 
    template<typename... T> 
    size_t function(T&&... nums) 
    { 
    static_assert(sizeof...(nums) == N, "Wrong number of arguments"); 
    std::array<size_t, N> arr = { std::forward<size_t>(nums)... }; 
    return arr[N-1]; 
    } 
}; 

int main(){ 
    A<5> a; 
    a.function(1,2,3,4,5); // OK 
    a.function(1,2,4,5); // Compile-time error 
} 

Ma, penso che ci sia un buon modo per far rispettare che in fase di compilazione. Vorrei solo usare assert(il.size() == N) nel codice di produzione per controllare la dimensione dell'elenco di inizializzazione.

Problemi correlati