So emulieren Sie die C-Array-Initialisierung “int arr[] = { e1, e2, e3, … }” Verhalten mit std::array?

Lesezeit: 13 Minuten

So emulieren Sie die C Array Initialisierung int arr e1 e2
Xeo

(Hinweis: Bei dieser Frage geht es darum, die Anzahl der Elemente nicht angeben zu müssen und dennoch die direkte Initialisierung verschachtelter Typen zuzulassen.)

Diese Frage diskutiert die verbleibenden Verwendungen für ein C-Array wie int arr[20];. In seiner Antwort zeigt @James Kanze eine der letzten Hochburgen von C-Arrays, ihre einzigartigen Initialisierungsmerkmale:

int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };

Wir müssen die Anzahl der Elemente nicht angeben, hurra! Iterieren Sie nun mit den C++11-Funktionen darüber std::begin und std::end von <iterator> (oder Ihre eigenen Varianten) und Sie müssen nicht einmal an seine Größe denken.

Gibt es nun (möglicherweise TMP) Möglichkeiten, dasselbe zu erreichen? std::array? Die Verwendung von Makros ist erlaubt, um es schöner aussehen zu lassen. 🙂

??? std_array = { "here", "be", "elements" };

Bearbeiten: Zwischenversion, zusammengestellt aus verschiedenen Antworten, sieht so aus:

#include <array>
#include <utility>

template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
  return { std::forward<T>(head), std::forward<Tail>(values)... };
}

// in code
auto std_array = make_array(1,2,3,4,5);

Und verwendet alle möglichen coolen C++11-Sachen:

  • Variadische Vorlagen
  • sizeof...
  • rvalue-Referenzen
  • perfekte Weiterleitung
  • std::arrayselbstverständlich
  • einheitliche Initialisierung
  • Weglassen des Rückgabetyps bei einheitlicher Initialisierung
  • Typschluss (auto)

Und ein Beispiel lässt sich finden Hier.

aber, wie @Johannes im Kommentar zur Antwort von @Xaade betont, können Sie mit einer solchen Funktion keine verschachtelten Typen initialisieren. Beispiel:

struct A{ int a; int b; };

// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };

Außerdem ist die Anzahl der Initialisierer auf die Anzahl der von der Implementierung unterstützten Funktions- und Vorlagenargumente beschränkt.

  • Variadische Methode. Es ist keine Initialisierung, eher eine Zuweisung, aber es kommt mir am nächsten. Um die Initialisierung zu erhalten, müssten Sie direkten Zugriff auf den Speicher haben.

    – Lee Louviere

    24. Mai 2011 um 17:03 Uhr

  • Anscheinend unterstützt C++0x die Initialisierungssyntax. Fantastisch. Es ist, als würde man C# ähnlicher werden, mit Sprachunterstützung für kompliziertere Unterstützung. Weiß jemand, ob wir formale Sprachunterstützung für Schnittstellen bekommen???

    – Lee Louviere

    24. Mai 2011 um 17:06 Uhr

  • @Downvoter: Grund?

    – Xeo

    2. Juni 2011 um 20:13 Uhr

  • Entschuldigung, was ist die Bedeutung von TMP in deiner frage?

    – Kevinarpe

    23. August 2016 um 14:36 ​​Uhr

  • @kevinarpe TMP steht wahrscheinlich für Template-Metaprogrammierung.

    – BeeOnRope

    28. Mai 2017 um 20:23 Uhr

So emulieren Sie die C Array Initialisierung int arr e1 e2
Pavel Minaev

Das Beste was mir einfällt ist:

template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
     return a;
}

auto a = make_array(1, 2, 3);

Dies erfordert jedoch, dass der Compiler NRVO ausführt und dann auch die Kopie des zurückgegebenen Werts überspringt (was ebenfalls zulässig, aber nicht erforderlich ist). In der Praxis würde ich erwarten, dass jeder C++-Compiler dies so optimieren kann, dass es so schnell ist wie eine direkte Initialisierung.

  • gcc 4.6.0 lässt den zweiten nicht kompilieren und beschwert sich über die Einschränkung der Konvertierung von double auf value_type, aber clang++ 2.9 ist mit beiden in Ordnung!

    – Kubbi

    24. Mai 2011 um 17:27 Uhr

  • Mit Antworten wie dieser verstehe ich am besten, was Bjarne über das Gefühl “wie in einer neuen Sprache” gesagt hat 🙂 Variadic-Templates, Late-Return-Specifier und Type-Deduktion in einem!

    – Matthias M.

    24. Mai 2011 um 17:33 Uhr


  • @Matthieu: Fügen Sie jetzt rvalue-Referenzen, perfekte Weiterleitung und einheitliche Initialisierung aus dem Code von @DeadMG hinzu, und Sie haben viele neue Funktionen festgelegt. :>

    – Xeo

    24. Mai 2011 um 17:36 Uhr

  • @Cubbi: Eigentlich ist g ++ genau hier – einschränkende Konvertierungen sind bei der Aggregatinitialisierung in C ++ 0x nicht zulässig (aber in C ++ 03 zulässig – eine bahnbrechende Änderung, die mir nicht bekannt war!). Das zweite entferne ich make_array Anruf.

    – Pavel Minaev

    24. Mai 2011 um 17:40 Uhr


  • @Cubbi, ja, aber das ist eine explizite Konvertierung – sie würde auch stille Downcasts und andere solche Dinge zulassen. Dies kann immer noch mit durchgeführt werden static_assert und etwas TMP, um zu erkennen, wann Tail ist nicht implizit konvertierbar in Tund dann mit T(tail)...aber das bleibt dem Leser als Übung überlassen 🙂

    – Pavel Minaev

    24. Mai 2011 um 17:46 Uhr

So emulieren Sie die C Array Initialisierung int arr e1 e2
Hündchen

Ich würde ein einfaches erwarten make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
    return { std::forward<T>(refs)... };
}

  • Entferne das std::array<ret, sizeof...(T)> auf der return Aussage. Das erzwingt sinnlos einen Move-Konstruktor für den Array-Typ (im Gegensatz zu einem Konstrukt ausT&&) in C++14 und C++11.

    – Yakk – Adam Nevraumont

    25. August 2016 um 20:25 Uhr


  • Ich finde es toll, wie C++-Leute das einfach nennen 🙂

    – Ciro Santilli Путлер Капут 六四事

    26. Oktober 2018 um 10:33 Uhr

1646926815 984 So emulieren Sie die C Array Initialisierung int arr e1 e2
Kerrek SB

Hier ist eine Lösung, die einige Ideen aus früheren Beiträgen kombiniert und sogar für verschachtelte Konstruktionen funktioniert (getestet in GCC4.6):

template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>
}

Seltsamerweise kann der Rückgabewert nicht zu einer Rvalue-Referenz gemacht werden, die für verschachtelte Konstruktionen nicht funktionieren würde. Wie auch immer, hier ist ein Test:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
                    );

std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(Für die letzte Ausgabe verwende ich meinen Pretty-Printer.)


Lassen Sie uns eigentlich die Typensicherheit dieser Konstruktion verbessern. Wir brauchen definitiv alle Typen, um gleich zu sein. Eine Möglichkeit besteht darin, eine statische Behauptung hinzuzufügen, die ich oben bearbeitet habe. Die andere Möglichkeit besteht darin, nur zu aktivieren make_array Wenn die Typen gleich sind, so:

template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
  return std::array<T, sizeof...(Args) + 1> { std::forward<T>
}

In jedem Fall benötigen Sie das Variadic all_same<Args...> Typ Eigenschaft. Hier ist es, verallgemeinernd aus std::is_same<S, T> (Beachten Sie, dass das Zerfallen wichtig ist, um das Mischen zu ermöglichen T, T&, T const & etc.):

template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };

Beachten Sie, dass make_array() gibt von copy-of-temporary zurück, die der Compiler (mit genügend Optimierungs-Flags!) als rvalue behandeln oder anderweitig wegoptimieren darf, und std::array ist ein Aggregattyp, sodass es dem Compiler freisteht, die bestmögliche Konstruktionsmethode auszuwählen.

Beachten Sie schließlich, dass Sie die Erstellung von Kopieren/Verschieben nicht vermeiden können, wenn make_array richtet den Initialisierer ein. Damit std::array<Foo,2> x{Foo(1), Foo(2)}; hat kein kopieren/verschieben, aber auto x = make_array(Foo(1), Foo(2)); hat zwei Kopien/Verschiebungen, an die die Argumente weitergeleitet werden make_array. Ich glaube nicht, dass Sie das verbessern können, da Sie dem Helfer keine variadische Initialisierungsliste lexikalisch übergeben können und Typ und Größe ableiten – wenn der Präprozessor a hatte sizeof... Funktion für variadische Argumente, vielleicht könnte das getan werden, aber nicht innerhalb der Kernsprache.

1646926816 315 So emulieren Sie die C Array Initialisierung int arr e1 e2
abgewischt

Verwendung der Trailing-Return-Syntax make_array weiter vereinfacht werden kann

#include <array>
#include <type_traits>
#include <utility>

template <typename... T>
auto make_array(T&&... t)
  -> std::array<std::common_type_t<T...>, sizeof...
{
  return {std::forward<T>
}

int main()
{
  auto arr = make_array(1, 2, 3, 4, 5);
  return 0;
}

Leider erfordert es für Aggregatklassen eine explizite Typangabe

/*
struct Foo
{
  int a, b;
}; */

auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});

In der Tat dies make_array Implementierung ist in aufgeführt sizeof…-Operator


c++17-Version

Dank an Template-Argumentableitung für Klassen-Templates Vorschlag, den wir mithilfe von Abzugshilfen loswerden können make_array Helfer

#include <array>

namespace std
{
template <typename... T> array(T... t)
  -> array<std::common_type_t<T...>, sizeof...
}

int main()
{
  std::array a{1, 2, 3, 4};
  return 0; 
}

Kompiliert mit -std=c++1z Flagge unter x86-64 gcc 7.0

1646926816 801 So emulieren Sie die C Array Initialisierung int arr e1 e2
Zizheng Tai

Ich weiß, es ist schon einige Zeit her, dass diese Frage gestellt wurde, aber ich habe das Gefühl, dass die vorhandenen Antworten immer noch einige Mängel aufweisen, daher möchte ich meine leicht modifizierte Version vorschlagen. Im Folgenden sind die Punkte aufgeführt, von denen ich denke, dass einige vorhandene Antworten fehlen.


1. Sie müssen sich nicht auf RVO verlassen

Einige Antworten erwähnen, dass wir uns auf RVO verlassen müssen, um das Konstruierte zurückzugeben array. Das ist nicht wahr; können wir nutzen Copy-List-Initialisierung um sicherzustellen, dass niemals Provisorien erstellt werden. Also statt:

return std::array<Type, …>{values};

wir sollten tun:

return {{values}};

2. Machen make_array ein constexpr Funktion

Dies ermöglicht es uns, konstante Arrays zur Kompilierzeit zu erstellen.

3. Es muss nicht überprüft werden, ob alle Argumente vom gleichen Typ sind

Erstens, wenn dies nicht der Fall ist, gibt der Compiler trotzdem eine Warnung oder einen Fehler aus, da die Listeninitialisierung keine Einschränkung zulässt. Zweitens, auch wenn wir uns wirklich entscheiden, es selbst zu tun static_assert Sache (vielleicht um eine bessere Fehlermeldung zu liefern), sollten wir wahrscheinlich trotzdem die Argumente vergleichen’ verfallen Typen statt Rohtypen. Zum Beispiel,

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array<int>(a, b, c);  // Will this work?

Wenn wir einfach sind static_asserting das a, bund c denselben Typ haben, schlägt diese Prüfung fehl, aber das ist wahrscheinlich nicht das, was wir erwarten würden. Stattdessen sollten wir ihre vergleichen std::decay_t<T> Typen (das sind alle intS)).

4. Leiten Sie den Array-Werttyp ab, indem Sie die weitergeleiteten Argumente zerfallen lassen

Dies ähnelt Punkt 3. Verwenden Sie dasselbe Code-Snippet, aber geben Sie diesmal den Werttyp nicht explizit an:

volatile int a = 0;
const int& b = 1;
int&& c = 2;

auto arr = make_array(a, b, c);  // Will this work?

Wir wollen wahrscheinlich eine machen array<int, 3>, aber die Implementierungen in den vorhandenen Antworten tun dies wahrscheinlich nicht. Was wir tun können, ist, anstatt a zurückzugeben std::array<T, …>gib a zurück std::array<std::decay_t<T>, …>.

Dieser Ansatz hat einen Nachteil: Wir können kein zurückgeben array des Lebenslauf-qualifizierten Werttyps nicht mehr. Aber meistens statt so etwas wie ein array<const int, …>würden wir a verwenden const array<int, …> ohnehin. Es gibt einen Kompromiss, aber ich denke, ein vernünftiger. Das C++17 std::make_optional geht auch so vor:

template< class T > 
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );

Unter Berücksichtigung der oben genannten Punkte ist eine voll funktionsfähige Implementierung von make_array in C++14 sieht so aus:

#include <array>
#include <type_traits>
#include <utility>

template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
    noexcept(noexcept(std::is_nothrow_constructible<
                std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
             >::value))

{
    return {{std::forward<T>
}

template<typename T>
constexpr std::array<std::decay_t<T>, 0> make_array() noexcept
{
    return {};
}

Verwendungszweck:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");

1646926817 51 So emulieren Sie die C Array Initialisierung int arr e1 e2
unterstrich_d

C++11 wird unterstützt diese Art der Initialisierung für (die meisten?) Std-Container.

(Lösung von @dyp)

Hinweis: erfordert C++14 (std::index_sequence). Obwohl man das umsetzen könnte std::index_sequence in C++11.

#include <iostream>

// ---

#include <array>
#include <utility>

template <typename T>
using c_array = T[];

template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
    return std::array<T, N>{{ std::move(src[Indices])... }};
}

template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
    return make_array(std::move(src), std::make_index_sequence<N>{});
}

// ---

struct Point { int x, y; };

std::ostream& operator<< (std::ostream& os, const Point& p) {
    return os << "(" << p.x << "," << p.y << ")";
}

int main() {
    auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});

    for (auto&& x : xs) {
        std::cout << x << std::endl;
    }

    return 0;
}

  • Ich habe die Standardinitialisierung der std::array-Elemente übersehen. Suche derzeit nach einer Lösung.

    – Gabriel García

    30. Dezember 2014 um 15:45 Uhr


  • @dyp Ich habe die Antwort mit deinem Code aktualisiert. Wenn Sie sich entscheiden, Ihre eigene Antwort zu schreiben, lassen Sie es mich wissen und ich werde meine herunterbringen. Danke.

    – Gabriel García

    30. Dezember 2014 um 17:40 Uhr


  • Nein, alles in Ordnung. Ein temporäres Array zu binden, um die Länge abzuleiten, ist Ihre Idee, und ich habe nicht überprüft, ob mein Code überhaupt kompiliert wird. Ich denke, es ist immer noch Ihre Lösung, und antworten Sie mit etwas Verfeinerung;) Man könnte jedoch argumentieren, dass ein Variadic keinen Vorteil hat make_array wie in Puppys Antwort.

    – dyp

    30. Dezember 2014 um 19:55 Uhr


  • Rechts. Darüber hinaus können Vorlagen keine Typen aus Initialisierungslisten ableiten, was eine der Anforderungen der Frage ist (Initialisierung mit verschachtelten Klammern).

    – Gabriel García

    4. Januar 2015 um 19:21 Uhr

988340cookie-checkSo emulieren Sie die C-Array-Initialisierung “int arr[] = { e1, e2, e3, … }” Verhalten mit std::array?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy