web-dev-qa-db-fra.com

Aliasing struct et array à la manière C++

Ceci est un suivi C++ pour une autre question de moi

A l'époque de la pré-ISO C, le code suivant n'aurait surpris personne:

struct Point {
    double x;
    double y;
    double z;
};
double dist(struct Point *p1, struct Point *p2) {
    double d2 = 0;
    double *coord1 = &p1->x;
    double *coord2 = &p2->x;
    int i;
    for (i=0; i<3; i++) {
        double d = coord2[i]  - coord1[i];    // THE problem
        d2 += d * d;
    }
    return sqrt(d2);
}

Malheureusement, cette ligne problématique utilise l'arithmétique de pointeur (p[i] étant par définition*(p + i)) en dehors de tout tableau non explicitement autorisé par la norme. Le projet 4659 pour C++ 17 dit dans 8.7 [expr.add]:

Si l'expression P pointe sur l'élément x [i] d'un objet tableau x avec n éléments, les expressions P + J et J + P (où J a la valeur j) désignent l'élément (éventuellement hypothétique) x [i + j] si 0 <= i + j <= n; sinon, le comportement n'est pas défini.

Et la note (non normative) 86 le rend encore plus explicite:

Un objet qui n'est pas un élément de tableau est considéré comme appartenant à un tableau à un seul élément à cette fin. UNE Le pointeur passé le dernier élément d'un tableau x de n éléments est considéré comme équivalent à un pointeur sur un élément hypothétique x [n] à cette fin.

La réponse acceptée de la question référencée utilise le fait que le langage C accepte type punning par des unions, mais je n’ai jamais pu trouver l’équivalent dans la norme C++. Donc, je suppose qu'une union contenant un membre struct anonyme et un tableau conduirait à Undefined Behaviour en C++ - ils sont différents langages ...

Question:

Quel pourrait être un moyen conforme d'itérer à travers les membres d'une structure comme s'ils étaient membres d'un tableau en C++? Je cherche une solution dans les versions actuelles (C++ 17), mais les solutions pour les versions plus anciennes sont également les bienvenues.

Avertissement:

Cela ne concerne évidemment que les éléments de même type, et le remplissage peut être détecté avec un simple assert, comme indiqué dans cet autre question , de sorte que le remplissage, l'alignement et les types mixtes ne sont pas mon problème ici.

25
Serge Ballesta

Utilisez un tableau constexpr de pointeur à membre:

#include <math.h>

struct Point {
    double x;
    double y;
    double z;
};

double dist(struct Point *p1, struct Point *p2) {
    constexpr double Point::* coords[3] = {&Point::x, &Point::y, &Point::z};

    double d2 = 0;
    for (int i=0; i<3; i++) {
        double d = p1->*coords[i] - p2->*coords[i];
        d2 += d * d;
    }
    return sqrt(d2);
}
23
Tobi

À mon humble avis, le plus simple consiste à implémenter operator[]. Vous pouvez créer un tableau d'assistance comme celui-ci ou simplement créer un commutateur ...

struct Point
{
    double const& operator[] (std::size_t i) const 
    {
        const std::array coords {&x, &y, &z};
        return *coords[i];
    }

    double& operator[] (std::size_t i) 
    {
        const std::array coords {&x, &y, &z};
        return *coords[i];
    }

    double x;
    double y;
    double z;
};

int main() 
{
    Point p {1, 2, 3};
    std::cout << p[2] - p[1];
    return 0;
}
16
Jaa-c
struct Point {
  double x;
  double y;
  double z;
  double& operator[]( std::size_t i ) {
    auto self = reinterpret_cast<uintptr_t>( this );
    auto v = self+i*sizeof(double);
    return *reinterpret_cast<double*>(v);
  }
  double const& operator[]( std::size_t i ) const {
    auto self = reinterpret_cast<uintptr_t>( this );
    auto v = self+i*sizeof(double);
    return *reinterpret_cast<double const*>(v);
  }
};

cela repose sur le fait qu'il n'y a pas de compression entre les doubles dans votre `struct. Affirmer que c'est difficile.

Une structure POD est une séquence d'octets garantis.

Un compilateur devrait pouvoir compiler [] avec les mêmes instructions (ou leur absence) qu'un accès brut à un tableau ou à une arithmétique de pointeur. Il y a peut problèmes dans lesquels cette optimisation se produit "trop ​​tard" pour que d'autres optimzations se produisent. Vérifiez donc le code sensible aux performances.

Il est possible que la conversion en char* ou std::byte* instable de uintptr_t soit valide, mais il existe un problème fondamental about si l'arithmétique de pointeur est autorisée dans ce cas.

2

Vous pouvez utiliser le fait que la conversion d'un pointeur sur intptr_t en effectuant des opérations arithmétiques, puis la remise de la valeur sur le type de pointeur est le comportement implemetation défini. Je crois que cela fonctionnera sur la plupart des compilateurs: 

template<class T>
T* increment_pointer(T* a){
  return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(a)+sizeof(T));
  }

Cette technique est la plus efficace, les optimiseurs ne semblent pas être en mesure de produire de manière optimale si une table d’utilisation cherche: assemblies-Comparison

0
Oliv