Comment émuler l'initialisation du tableau C "int arrr[] = {e1, e2, e3, ...}" comportement avec std::array?
(Note: cette question concerne le fait de ne pas avoir à spécifier le nombre d'éléments et de permettre tout de même aux types imbriqués d'être directement initialisés.)
cette question discute les utilisations laissées pour un tableau C comme int arr[20];
. Sur sa réponse , @James Kanze montre l'un des derniers bastions des tableaux C, c'est unique caractéristiques d'initialisation:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
nous pas besoin de préciser le nombre d'éléments, hourra! Maintenant itérez-le avec les fonctions C++11 std::begin
et std::end
de <iterator>
( ou vos propres variantes ) et vous n'avez même pas besoin de penser à sa taille.
maintenant, y a-t-il des moyens (éventuellement TMP) pour obtenir la même chose avec std::array
? Utilisation de macros permis de le faire paraître plus agréable. :)
??? std_array = { "here", "be", "elements" };
modifier : version intermédiaire, compilée à partir de diverses réponses, ressemble à ceci:
#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);
et emploie toute sorte de cool C++11 stuff:
- Variadic Templates
-
sizeof...
- références rvalue
- perfect forwarding
-
std::array
, bien sûr - initialisation uniforme
- omettant le type de retour avec une initialisation uniforme
- type inférence (
auto
)
Et un exemple peut être trouvé ici .
cependant , comme @Johannes le souligne dans le commentaire sur la réponse de @Xaade, vous ne pouvez pas initialiser les types imbriqués avec une telle fonction. Exemple:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };
aussi, le nombre d'initialiseurs est limité au nombre d'arguments de fonction et de modèle pris en charge par la mise en œuvre.
9 réponses
le mieux que je puisse penser est:
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);
cependant, cela nécessite que le compilateur fasse NRVO, et puis sauter également la copie de la valeur retournée (qui est également légale mais pas nécessaire). En pratique, je m'attendrais à ce que n'importe quel compilateur C++ soit capable de l'optimiser de telle sorte qu'il soit aussi rapide que l'initialisation directe.
Je m'attendais à un simple 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)... } };
}
combinant quelques idées des billets précédents, voici une solution qui fonctionne même pour les constructions emboîtées (testé dans 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>(t), std::forward<Args>(args)...};
}
étrangement, ne peut pas faire la valeur de retour une référence de valeur, qui ne fonctionnerait pas pour les constructions imbriquées. Bref, voici un 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]]]
(pour la dernière sortie j'utilise mon pretty-printer .)
En fait, améliorons la sécurité de type de cette construction. Nous avons vraiment besoin que tous les types soient les mêmes. Une façon est d'ajouter une assertion statique, que j'ai éditée ci-dessus. L'autre façon est d'activer make_array
quand les types sont les mêmes, comme suit:
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>(t), std::forward<Args>(args)...};
}
dans tous les cas, vous aurez besoin du trait de type variadique all_same<Args...>
. La voici, généralisant de std::is_same<S, T>
(notez que la décomposition est importante pour permettre le mélange de 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; };
notez que make_array()
retourne par copie-de-temporaire, que le compilateur (avec des indicateurs d'optimisation suffisants! std::array
est un agrégat, de sorte que le compilateur est libre de choisir la meilleure méthode de construction possible.
enfin, notez que vous ne pouvez pas éviter la construction copie / déplacement lorsque make_array
met en place l'initialiseur. So std::array<Foo,2> x{Foo(1), Foo(2)};
n'a pas de copie/mouvement, mais auto x = make_array(Foo(1), Foo(2));
a deux copies/mouvements car les arguments sont transmis à make_array
. Je ne pense pas que vous pouvez améliorer sur cela, parce que vous ne pouvez pas passer une liste d'initialiseur variadique lexiquement à l'Assistant et déduire le type et la taille-si le préprocesseur avait une fonction sizeof...
pour les arguments variadique, peut-être que cela pourrait être fait, mais pas dans le langage de base.
en utilisant la syntaxe de retour arrière make_array
peut être encore simplifié
#include <array>
#include <type_traits>
#include <utility>
template <typename... T>
auto make_array(T&&... t)
-> std::array<std::common_type_t<T...>, sizeof...(t)>
{
return {std::forward<T>(t)...};
}
int main()
{
auto arr = make_array(1, 2, 3, 4, 5);
return 0;
}
malheureusement pour les classes agrégées, il faut une spécification de type explicite
/*
struct Foo
{
int a, b;
}; */
auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});
en fait cette make_array
mise en œuvre est énumérée dans sizeof... exploitant
C++17 version
merci à déduction d'argument de modèle pour les modèles de classe proposition nous pouvons utiliser des guides de déduction pour se débarrasser de make_array
helper
#include <array>
namespace std
{
template <typename... T> array(T... t)
-> array<std::common_type_t<T...>, sizeof...(t)>;
}
int main()
{
std::array a{1, 2, 3, 4};
return 0;
}
compilé avec -std=c++1z
drapeau sous x86-64 gcc 7.0
C++11 supportera cette manière d'initialisation pour (most?) std conteneurs.
(Solution de @dyp)
Note: nécessite C++14 ( std::index_sequence
). Bien qu'on puisse implémenter std::index_sequence
en 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;
}
je sais que cela fait un certain temps que cette question a été posée, mais je pense que les réponses existantes ont encore quelques lacunes, donc je voudrais proposer ma version légèrement modifiée. Suivantes sont les points que je pense que certaines réponses sont manquantes.
1. Pas besoin de compter sur RVO
certaines réponses mentionnent que nous devons compter sur RVO pour retourner le array
construit . Ce n'est pas vrai; nous pouvons faire utilisation de copy-list-initialization pour garantir qu'il n'y aura jamais de temporaires créés. Donc au lieu de:
return std::array<Type, …>{values};
nous devrions faire:
return {{values}};
2. Faire make_array
a constexpr
fonction
cela nous permet de créer des tableaux de constantes de temps de compilation.
3. Pas besoin de vérifier que tous les arguments sont du même type
D'abord, s'ils ne le sont pas, le compilateur émettra un avertissement ou une erreur de toute façon parce que l'initialisation par liste ne permet pas de rétrécir. Deuxièmement, même si nous décidons vraiment de faire notre propre static_assert
chose (peut-être pour fournir un meilleur message d'erreur), nous devrions encore probablement comparer les arguments types décomposés plutôt que les types bruts. Par exemple,
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array<int>(a, b, c); // Will this work?
si nous sommes simplement static_assert
ing que a
, b
, et c
ont le même type, alors cette vérification échouera, mais ce n'est probablement pas ce à quoi on s'attend. Au lieu de cela, nous devrions comparer leurs types std::decay_t<T>
(qui sont tous int
s)).
4. Déduisez le type de valeur du tableau en décomposant les arguments transmis
similaire au point 3. En utilisant le même snippet de code, mais ne spécifiez pas le type de valeur explicitement cette fois:
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array(a, b, c); // Will this work?
nous voulons probablement faire un array<int, 3>
, mais les implémentations dans les réponses existantes ne le font probablement pas toutes. Ce que nous pouvons faire, c'est , au lieu de retourner un std::array<T, …>
, retourner un std::array<std::decay_t<T>, …>
.
il y a un inconvénient à cette approche: nous ne pouvons plus retourner un array
de type cv-qualified value. Mais la plupart du temps , au lieu de quelque chose comme un array<const int, …>
, nous utilisions un const array<int, …>
de toute façon. Il y a un compromis, mais je pense qu'il est raisonnable. Le C++17 std::make_optional
prend également cette approche:
template< class T >
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
compte tenu de ce qui précède, une mise en œuvre complète de make_array
en C++14 ressemble à ceci:
#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>(t), std::forward<Ts>(ts)...}};
}
template<typename T>
constexpr std::array<std::decay<T>_t, 0> make_array() noexcept
{
return {};
}
Utilisation:
constexpr auto arr = make_array(make_array(1, 2),
make_array(3, 4));
static_assert(arr[1][1] == 4, "!");
créer un type de fabricant de tableau.
il télécharge operator,
pour générer un modèle d'expression enchaînant chaque élément aux références via précédentes.
ajouter une finish
fonction libre qui prend le fabricant de tableau et génère un tableau directement à partir de la chaîne de références.
la syntaxe doit ressembler à quelque chose comme ceci:
auto arr = finish( make_array<T>->* 1,2,3,4,5 );
il ne permet pas {}
basé construction, comme seul operator=
le fait. Si vous êtes prêt à utiliser =
nous pouvons le faire fonctionner:
auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );
ou
auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );
aucune de ces solutions ne semble bonne.
L'utilisation de variardics vous limite à la limite imposée par le compilateur sur le nombre de varargs et bloque l'utilisation récursive de {}
pour les sous-structures.
finalement, il n'y a vraiment pas de bonne solution.
ce que je fais c'est écrire mon code pour qu'il consume à la fois T[]
et std::array
données agnostiquement -- il ne se soucie pas de ce que je l'alimente. Parfois, cela signifie que mon code d'expédition doit soigneusement transformer les matrices []
en std::array
de manière transparente.