Warum hat std :: array keinen Konstruktor, der einen Wert für das zu füllende Array annimmt?

77

Ist das Fehlen von

std::array<T,size>::array(const T& value);

ein Versehen? Es scheint mir sehr nützlich zu sein, und dynamische Container (wie std::vector) haben einen ähnlichen Konstruktor.

Ich bin mir dessen voll bewusst

std::array<T,size>::fill(const T& value);

Dies ist jedoch kein Konstruktor, und der Speicher wird zuerst auf Null gesetzt. Was ist, wenn ich will, dass alle so sind -1wie dieser Typ ?

rubenvb
quelle
1
"und der Speicher wird zuerst auf Null gesetzt" Sind Sie sicher, dass dies wahr ist?
Tohava
3
Es wird nicht zuerst auf Null gesetzt, es sei denn, Sie fragen danach.
R. Martinho Fernandes
1
Neben dem Aggregat -Argument aus allen Antworten, könnte es auch eine konzeptionelle Überlegung sein. Ein Füllkonstruktor würde wahrscheinlich die Tatsache verbergen, dass er die einzelnen Elemente nicht wirklich konstruiert. Es wird in erster Linie die aggregierte Initialisierung aufrufen und dann den Wert in die Elemente kopieren. Es kann die Elemente nicht sofort kopieren und konstruieren (im Gegensatz zu beispielsweise a std::vector). Da dies also immer gleichbedeutend wäre array(); array.fill();, verbirgt das Weglassen des Konstruktors diese Tatsache nicht.
Christian Rau
1
Ebenfalls relevant: stackoverflow.com/questions/18497122/…
kennytm

Antworten:

54

std::array ist von Natur aus ein Aggregat und hat daher keine vom Benutzer deklarierten Konstruktoren.

Wie Sie sagen, können Sie fillnach der Standardkonstruktion verwenden. Da es sich um ein Aggregat handelt, wird der Speicher durch die Standardkonstruktion nicht auf Null gesetzt, sondern nicht initialisiert (wenn der enthaltene Typ trivial initialisierbar ist).

Mike Seymour
quelle
1
Also ist diese Seite falsch? Es heißt ausdrücklich, dass die Elemente des Arrays standardmäßig initialisiert sind.
Rubenvb
15
Die Standardinitialisierung ist no-init für PODs und der Standardkonstruktor für alles andere, was ich glaube - abhängig vom Deklarationspunkt.
Xeo
1
@rubenvb: Ja, sie werden standardmäßig initialisiert und nicht wertinitialisiert. Wenn sie also trivial initialisierbar sind, bleiben sie nicht initialisiert.
Mike Seymour
Ah ja. Das bleibt immer noch ein sehr ungezogener Teil der Standard-IMO, die zwischen Benutzer- und eingebauten Typen unterscheidet:/
rubenvb
11
@rubenvb Die primitiven Typen haben alle eine triviale Standardinitialisierung. Benutzerdefinierte Typen können sich auf Wunsch genauso verhalten. Das ist kein Unterschied, es ist Konsistenz.
Casey
23

Beachten Sie, dass Sie diesen Konstruktortyp effizient simulieren können, indem Sie die Tatsache ausnutzen, dass das Array nicht mit Null initialisiert ist und über einen Kopierkonstruktor verfügt.

template <size_t N, class T>
array<T,N> make_array(const T &v) {
    array<T,N> ret;
    ret.fill(v);
    return ret;
}

auto a = make_array<20>('z');
tohava
quelle
Lassen Sie uns die Aufrufseite nicht mit nicht benötigten Vorlagenparametern aufblähen ;-).
Rubenvb
4
charkann gefolgert werden, so können Sie einfach schreiben make_array<20>('z') stattmake_array<20,char>('z')
Nawaz
@Nawaz oh, noch besser. Ich hätte fragen sollen, warum es make_arraystattdessen keine gibt:-)
rubenvb
1
@Walter: Sie konnten keine Referenz irgendeiner Art zurückgeben, da Sie eine Referenz auf eine lokale Variable zurückgeben würden.
GManNickG
3
Dies wird nicht funktionieren, wenn Tes nicht standardmäßig konstruierbar ist, und wenn Sie den Füllkonstruktor dringend benötigen.
Chris Beck
12

Sie können dafür verwenden std::index sequence:

namespace detail
{

    template <typename T, std::size_t...Is>
    constexpr std::array<T, sizeof...(Is)>
    make_array(const T& value, std::index_sequence<Is...>)
    {
        return {{(static_cast<void>(Is), value)...}};
    }
}

template <std::size_t N, typename T>
constexpr std::array<T, N> make_array(const T& value)
{
    return detail::make_array(value, std::make_index_sequence<N>());
}

Demo

std::make_index_sequence ist C ++ 14, kann aber in C ++ 11 implementiert werden.

static_cast<void>(Is)ist mit dem Bösen umzugehen operator,, Tdas bieten könnte.

Jarod42
quelle
3
Dies ist die nützlichste Antwort. Sie funktioniert auch, wenn sie Tnicht standardmäßig konstruierbar ist. In diesem Fall sind die anderen Antworten nicht ausreichend.
Chris Beck
1
@ ChrisBeck Konnte nicht mehr zustimmen. Das war genau der Fall für mich und ich war frustriert über die anderen Antworten. Zeigt, dass Up-Votes nicht alles sind.
Jonas Greitemann
Aufgrund der Verwendung der Vorlagenrekursion wird dies nicht für große Arrays kompiliert. Wenn ich ein Millionen-Elemente-Array von T ohne Standardkonstruktor benötige, habe ich dann kein Glück? Sogar ein C-Array würde für mich funktionieren.
Michał Brzozowski
@ MichałBrzozowski: std::index_sequencekönnte mit Logarithmus-Instanziierung anstelle von linear implementiert werden. und der Compiler verfügt möglicherweise über "Eigenheiten" , um nur eine Instanziierung durchzuführen. Dann gibt es keine Rekursion mehr.
Jarod42
1
@ MichałBrzozowski: Sie können immer noch wechseln std::vector, und reserveganz Größe und emplace_backin einem loop.As für ein Million-Element, würde Stapel problematisch sein , als Speicher in der Praxis begrenzt.
Jarod42
10

Erstens ist es nicht std::array<T> , dass std::array<T,N>hier der NIntegralausdruck der Kompilierungszeitkonstante ist.

Zweitens std::arraywird durch Design aggregiert. Es hat also nichts, was es nicht aggregiert, weshalb es keinen Konstruktor ... und keinen Destruktor, keine virtuellen Funktionen usw. hat.

Nawaz
quelle