web-dev-qa-db-ja.com

C ++の方法でエイリアス構造と配列を作成する

これは、 私の別の質問のC++フォローアップです

ISO以前のCの昔は、次のコードで誰も驚かなかったでしょう。

_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);
}
_

残念ながら、この問題のある行は、ポインタの算術を使用しています(_p[i]_はによって定義されています*(p + i)))標準では明示的に許可されていない配列の外にあります。 C++ 17の4659は8.7で述べています[expr.add]:

式Pがn個の要素を持つ配列オブジェクトxの要素x [i]を指す場合、式P + JおよびJ + P(Jは値jを持っています)は、(おそらく架空の)要素x [i + j] 0 <= i + j <= nの場合;それ以外の場合、動作は未定義です。

そして(非規範的)ノート86はそれをさらに明確にします:

配列要素ではないオブジェクトは、この目的のために単一要素配列に属していると見なされます。 n要素の配列xの最後の要素を過ぎたポインタは、この目的のための仮想要素x [n]へのポインタと同等であると見なされます。

参照された質問の受け入れられた回答は、C言語がユニオンを介して型のパンニングを受け入れるという事実を使用していますが、C++標準では同等のものを見つけることができませんでした。したがって、匿名の構造体メンバーと配列を含む共用体がC++で_Undefined Behaviour_につながると思います—それらは異なる言語です...

質問:

構造体のメンバーをC++の配列のメンバーであるかのように反復処理するための適合的な方法は何でしょうか?現在の(C++ 17)バージョンの方法を探していますが、古いバージョンの解決策も歓迎します。

免責事項:

それは明らかに同じタイプの要素にのみ適用され、パディングは他の question に示されているように単純なassertで検出できるため、パディング、配置、および混合タイプここでは私の問題ではありません。

30
Serge Ballesta

メンバーへのポインターのconstexpr配列を使用します。

#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);
}
25
Tobi

IMHO最も簡単な方法は、operator[]を実装することです。このようなヘルパー配列を作成するか、スイッチを作成するだけです...

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;
}
17
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);
  }
};

これは、 `structのdoublesの間にパッキングがないことに依存しています。それを主張するのは難しい。

POD構造体は、保証された一連のバイトです。

コンパイラーは、[]を、生の配列アクセスまたはポインター演算と同じ命令(またはその欠如)にコンパイルできる必要があります。 may他の最適化が発生するのにこの最適化が「遅すぎる」場合、いくつかの問題が発生する可能性があるため、パフォーマンスに敏感なコードを再確認してください。

char*std::byte*またはuintptr_t instedへの変換は有効である可能性がありますが、 コアの問題があります この場合、ポインター演算が許可されているかどうかについて。

ポインタをintptr_tにキャストして算術演算を行ってから、値をポインタタイプにキャストし直すという事実を使用できます実装定義の動作。私はそれがほとんどのコンパイラで機能すると信じています:

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

この手法は最も効率的であり、オプティマイザは、1つの使用テーブルを検索すると最適な結果を生成できないようです: assemblies-comparison

0
Oliv