Comment émuler le comportement de l'initialisation du tableau C "int arr [] = {e1, e2, e3,…}" avec std :: array?

137

(Remarque: cette question concerne le fait de ne pas avoir à spécifier le nombre d'éléments et de toujours permettre aux types imbriqués d'être directement initialisés.)
Cette question traite des utilisations restantes pour un tableau C comme int arr[20];. Sur sa réponse , @James Kanze montre l'un des derniers bastions des tableaux C, ses caractéristiques d'initialisation uniques:

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

Nous n'avons pas à spécifier le nombre d'éléments, hourra! Maintenant, parcourez-le avec les fonctions C ++ 11 std::beginet std::endfrom <iterator>( ou vos propres variantes ) et vous n'avez même jamais besoin de penser à sa taille.

Maintenant, y a-t-il des moyens (éventuellement TMP) pour obtenir la même chose avec std::array? L'utilisation de macros a permis de le rendre plus joli. :)

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

Edit : La 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 utilise toutes sortes de trucs sympas C ++ 11:

  • Modèles Variadic
  • sizeof...
  • références rvalue
  • acheminement parfait
  • std::array, bien sûr
  • initialisation uniforme
  • omettre le type de retour avec une initialisation uniforme
  • inférence de type ( auto)

Et un exemple peut être trouvé ici .

Cependant , comme le souligne @Johannes dans le commentaire sur la réponse de @ Xaade, vous ne pouvez pas initialiser des 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} };

En outre, le nombre d'initialiseurs est limité au nombre d'arguments de fonction et de modèle pris en charge par l'implémentation.

Xeo
la source
Méthode variadique. Ce n'est pas une initialisation, plutôt une affectation, mais c'est le plus proche auquel je puisse arriver. Pour obtenir l'initialisation, vous devez avoir un accès direct à la mémoire.
Lee Louviere
Apparemment, C ++ 0x prend en charge la syntaxe d'initialisation. Impressionnant. C'est comme devenir plus comme C #, avec un support de langage pour un support plus compliqué. Tout le monde sait si nous obtenons un support de langage formel pour les interfaces ???
Lee Louviere
10
@Downvoter: Raison?
Xeo
1
Excuses, quel est le sens de TMPvotre question?
kevinarpe
1
@kevinarpe TMP est probablement synonyme de métaprogrammation de modèles .
BeeOnRope

Réponses:

63

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, puis ignore également la copie de la valeur retournée (ce qui est également légal mais non obligatoire). En pratique, je m'attendrais à ce que n'importe quel compilateur C ++ puisse l'optimiser de telle sorte qu'il soit aussi rapide que l'initialisation directe.

Pavel Minaev
la source
gcc 4.6.0 ne laisse pas le second se compiler, se plaignant de la réduction de la conversion de double à value_type, mais clang ++ 2.9 est OK avec les deux!
Cubbi
21
C'est avec des réponses comme celle-ci que je comprends le plus ce que Bjarne a dit sur le fait de se sentir "comme un nouveau langage" :) Modèles variadiques, spécificateur de retour tardif et déduction de type tout-en-un!
Matthieu M.
@Matthieu: Ajoutez maintenant des références rvalue, un transfert parfait et une initialisation uniforme à partir du code de @ DeadMG et vous avez de nombreuses nouvelles fonctionnalités. :>
Xeo
1
@Cubbi: en fait, g ++ est ici - les conversions restreintes ne sont pas autorisées dans l'initialisation agrégée en C ++ 0x (mais autorisées en C ++ 03 - un changement radical dont je n'étais pas au courant!). Je vais supprimer le deuxième make_arrayappel.
Pavel Minaev
@Cubbi, ouais, mais c'est une conversion explicite - cela permettrait également des downcasts silencieux et d'autres choses du genre.Cela peut toujours être fait en utilisant static_assertet du TMP pour détecter quand il Tailn'est pas implicitement convertible en T, puis en utilisant T(tail)..., mais cela reste comme exercice pour le lecteur :)
Pavel Minaev
39

Je m'attendrais à 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)... } };
    return { std::forward<T>(refs)... };
}
Chiot
la source
1
Supprimez le std::array<ret, sizeof...(T)>sur la returndéclaration. Cela force inutilement un constructeur de déplacement sur le type de tableau à exister (par opposition à une construction à partir de T&&) en C ++ 14 et C ++ 11.
Yakk - Adam Nevraumont
8
J'aime la façon dont les gens C ++ appellent cela simple :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
20

En combinant quelques idées des articles précédents, voici une solution qui fonctionne même pour les constructions imbriquées (testée 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 de la valeur de retour une référence rvalue, 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 ma jolie imprimante .)


En fait, améliorons la sécurité de type de cette construction. Nous avons certainement besoin que tous les types soient identiques. Une façon est d'ajouter une assertion statique, que j'ai modifiée ci-dessus. L'autre façon est de n'activer que make_arraylorsque les types sont identiques, comme ceci:

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 all_same<Args...>trait de type variadique . Ici , il est, de généralisait std::is_same<S, T>(notez que putréfaction est important 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 les make_array()retours par copie de temporaire, que le compilateur (avec suffisamment d'indicateurs d'optimisation!) Est autorisé à traiter comme une rvalue ou à optimiser autrement, et std::arrayest un type d'agrégat, le compilateur est donc libre de choisir la meilleure méthode de construction possible .

Enfin, notez que vous ne pouvez pas éviter la construction copier / déplacer lors de la make_arrayconfiguration de l'initialiseur. Donc, std::array<Foo,2> x{Foo(1), Foo(2)};il n'y a pas de copie / déplacement, mais auto x = make_array(Foo(1), Foo(2));a deux copies / déplacements lorsque les arguments sont transférés make_array. Je ne pense pas que vous puissiez améliorer cela, car vous ne pouvez pas passer une liste d'initialiseurs variadiques lexicalement à l'assistant et en déduire le type et la taille - si le préprocesseur avait une sizeof...fonction pour les arguments variadiques, peut-être que cela pourrait être fait, mais pas dans la langue de base.

Kerrek SB
la source
13

L'utilisation de la syntaxe de retour de fin make_arraypeut être encore simplifiée

#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 nécessite 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_arrayimplémentation est répertoriée dans sizeof ... opérateur


version c ++ 17

Grâce à la déduction des arguments de modèle pour la proposition de modèles de classe, nous pouvons utiliser des guides de déduction pour se débarrasser de l' make_arrayassistant

#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 le -std=c++1zdrapeau sous x86-64 gcc 7.0

essuyé
la source
6
C ++ 17 devrait déjà avoir un guide de déduction pour cela: en.cppreference.com/w/cpp/container/array/deduction_guides
underscore_d
6

C ++ 11 prendra en charge cette manière d'initialiser pour (la plupart?) Les conteneurs std.

Richard
la source
1
Cependant, je pense qu'OP ne veut pas spécifier la taille du tableau, mais la taille est un paramètre de modèle de std :: array. Vous avez donc besoin de quelque chose comme std :: array <unsigned int, 5> n = {1,2,3,4,5};
juanchopanza
std::vector<>n'a pas besoin de l'entier explicite, et je ne sais pas pourquoi std::array.
Richard
@Richard, car std :: vector a une taille dynamique et std :: array a une taille fixe. Voir ceci: en.wikipedia.org/wiki/Array_(C%2B%2B)
juanchopanza
@juanchopanza mais la {...}syntaxe implique une étendue constante au moment de la compilation, donc le contrôleur devrait être capable de déduire l'étendue.
Richard
1
std::initializer_list::sizen'est pas une constexprfonction et ne peut donc pas être utilisée de cette manière. Il y a cependant des plans de libstdc ++ (l'implémentation livrée avec GCC) pour avoir leur version constexpr.
Luc Danton le
6

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 des lacunes, alors j'aimerais proposer ma version légèrement modifiée. Voici les points pour lesquels je pense que certaines réponses existantes manquent.


1. Pas besoin de compter sur RVO

Certaines réponses mentionnent que nous devons nous fier à RVO pour renvoyer le construit array. Ce n'est pas vrai; nous pouvons utiliser l' initialisation de la liste de copies pour garantir qu'il n'y aura jamais de création de temporaires. Donc au lieu de:

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

nous devrions faire:

return {{values}};

2. Créer make_arrayune constexprfonction

Cela nous permet de créer des tableaux de constantes au moment de la compilation.

3. Pas besoin de vérifier que tous les arguments sont du même type

Tout d'abord, s'ils ne le sont pas, le compilateur émettra un avertissement ou une erreur de toute façon car l'initialisation de la liste ne permet pas de rétrécir. Deuxièmement, même si nous décidons vraiment de faire notre propre static_asserttruc (peut-être pour fournir un meilleur message d'erreur), nous devrions probablement comparer les types décomposés des arguments 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 faisons simplement static_assertcela a, bet que nous cavons le même type, alors cette vérification échouera, mais ce n'est probablement pas ce à quoi nous nous attendions. Au lieu de cela, nous devrions comparer leurs std::decay_t<T>types (qui sont tous des ints)).

4. Déduisez le type de valeur du tableau en décomposant les arguments transmis

Ceci est similaire au point 3. En utilisant le même extrait 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 échouent probablement toutes à le faire. Ce que nous pouvons faire est, au lieu de renvoyer un std::array<T, …>, renvoyer unstd::array<std::decay_t<T>, …> .

Il y a un inconvénient à cette approche: nous ne pouvons plus renvoyer un arraytype de valeur qualifié cv. Mais la plupart du temps, au lieu de quelque chose comme un array<const int, …>, nous utiliserions unconst array<int, …> quand même . Il y a un compromis, mais je pense qu'il est raisonnable. Le C ++ 17std::make_optional adopte également cette approche:

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

En tenant compte des points ci-dessus, une implémentation fonctionnelle complète de make_arrayC ++ 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>, 0> make_array() noexcept
{
    return {};
}

Usage:

constexpr auto arr = make_array(make_array(1, 2),
                                make_array(3, 4));
static_assert(arr[1][1] == 4, "!");
Zizheng Tai
la source
5

(Solution par @dyp)

Remarque: nécessite C ++ 14 ( std::index_sequence). Bien que l'on puisse implémenter std::index_sequenceen 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;
}
Gabriel Garcia
la source
J'ai oublié l'initialisation par défaut des éléments std :: array. Actuellement à la recherche d'un correctif.
Gabriel Garcia
@dyp J'ai mis à jour la réponse avec votre code. Si vous décidez de rédiger votre propre réponse, faites-le moi savoir et je réduirai la mienne. Je vous remercie.
Gabriel Garcia
1
Non c'est bon. Lier un tableau temporaire pour en déduire la longueur est votre idée, et je n'ai même pas vérifié si mon code se compile. Je pense que c'est toujours votre solution, et la réponse, avec un peu de raffinement;) On pourrait cependant dire qu'il n'y a aucun avantage à une variadique make_arraycomme dans la réponse de Puppy, cependant.
dyp
Droite. De plus, les modèles ne peuvent pas déduire les types des listes d'initialiseurs, ce qui est l'une des exigences de la question (initialisation accolée imbriquée).
Gabriel Garcia
1

Implémentation compacte С ++ 17.

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
Peter
la source
0

Si std :: array n'est pas une contrainte et si vous avez Boost, jetez un œil à list_of(). Ce n'est pas exactement comme l'initialisation de tableau de type C que vous souhaitez. Mais proche.

yasouser
la source
c'en est une bonne. et une question similaire sur son utilisation pour l'attribution de structures imbriquées peut être trouvée ici Using-assign-map-list-of-for-complex-types
Assambar
0

Créez un type de fabricant de baie.

Il surcharge operator, pour générer un modèle d'expression chaînant chaque élément au précédent via des références.

Ajouter un finish fonction gratuite qui prend le générateur de tableaux et génère un tableau directement à partir de la chaîne de références.

La syntaxe devrait ressembler à ceci:

auto arr = finish( make_array<T>->* 1,2,3,4,5 );

Il ne permet pas la {}construction basée, comme le fait seulement operator=. 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 ressemble à de bonnes solutions.

L'utilisation de variardics vous limite à la limite imposée par le compilateur au nombre de varargs et bloque l'utilisation récursive de {}pour les sous-structures.

En fin de compte, il n'y a vraiment pas de bonne solution.

Ce que je fais, c'est d'écrire mon code pour qu'il consomme à la fois T[]et les std::arraydonnées de manière agnostique - peu importe que je le nourris. Parfois, cela signifie que mon code de transfert doit soigneusement transformer les []tableaux en std::arrays de manière transparente.

Yakk - Adam Nevraumont
la source
1
"Cela ne ressemble pas à de bonnes solutions." Est-ce que je dirais aussi: p
caps