9

これは、C++ 言語のさまざまな癖に関する興味深い質問です。ポイントの配列を四角形の角で埋めることになっている関数のペアがあります。それには 2 つのオーバーロードがあります。1 つは を受け取り、Point[5]もう 1 つは を受け取りますPoint[4]。5 ポイント バージョンは閉じた多角形を参照しますが、4 ポイント バージョンは 4 つのコーナー、ピリオドだけが必要な場合です。

明らかに、ここには作業の重複があるので、4 ポイント バージョンを使用して 5 ポイント バージョンの最初の 4 ポイントを入力できるようにしたいので、そのコードは複製しません。(重複するほどではありませんが、コードをコピーして貼り付けるたびにひどいアレルギー反応を起こすので、それは避けたいと思います。)

T[m]問題は、C++ はa をT[n]whereに変換するという考えを気にしていないようですn < mstatic_cast何らかの理由でタイプに互換性がないと考えているようです。reinterpret_castもちろんうまく処理できますが、危険な動物であり、原則として、可能な限り避けたほうがよいでしょう。

だから私の質問は、配列タイプが同じであるサイズの配列をより小さなサイズの配列にキャストするタイプセーフな方法はありますか?

[編集] コード、はい。パラメーターは実際には単なるポインターではなく配列への参照であるため、コンパイラーは型の違いを認識していることを言及する必要がありました。

void RectToPointArray(const degRect& rect, degPoint(&points)[4])
{
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon;
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon;
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon;
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon;
}
void RectToPointArray(const degRect& rect, degPoint(&points)[5])
{
    // I would like to use a more type-safe check here if possible:
    RectToPointArray(rect, reinterpret_cast<degPoint(&)[4]> (points));
    points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon;
}

[Edit2] 参照渡し配列を渡すポイントは、呼び出し元が正しい「出力パラメーター」を渡していることを少なくとも漠然と確信できるようにすることです。

4

6 に答える 6

5

過負荷でこれを行うのは良い考えではないと思います。関数の名前は、開いている配列を埋めるかどうかを呼び出し元に伝えません。また、呼び出し元がポインターしか持っておらず、座標を塗りつぶしたい場合はどうすればよいでしょうか (複数の四角形を塗りつぶして、異なるオフセットでより大きな配列の一部にしたいとしましょう)。

これを 2 つの関数で行い、それらにポインタを持たせます。サイズはポインターの型の一部ではありません

void fillOpenRect(degRect const& rect, degPoint *p) { 
  ... 
}

void fillClosedRect(degRect const& rect, degPoint *p) { 
  fillOpenRect(rect, p); p[4] = p[0]; 
}

これの何が問題なのかわかりません。あなたの再解釈キャストは実際にはうまくいくはずです(何がうまくいかないのかわかりません-アライメントと表現の両方が正しいので、ここでは単なる形式的な未定義は現実には実行されないと思います)が、私が言ったように上記のように、これらの関数が参照によって配列を取るようにする正当な理由はないと思います。


一般的にやりたい場合は、出力イテレータで書くことができます

template<typename OutputIterator> 
OutputIterator fillOpenRect(degRect const& rect, OutputIterator out) { 
  typedef typename iterator_traits<OutputIterator>::value_type value_type;
  value_type pt[] = { 
    { rect.nw.lat, rect.nw.lon },
    { rect.nw.lat, rect.se.lon },
    { rect.se.lat, rect.se.lon },
    { rect.se.lat, rect.nw.lon }
  };
  for(int i = 0; i < 4; i++)
    *out++ = pt[i];
  return out;
}

template<typename OutputIterator>
OutputIterator fillClosedRect(degRect const& rect, OutputIterator out) { 
  typedef typename iterator_traits<OutputIterator>::value_type value_type;
  out = fillOpenRect(rect, out); 

  value_type p1 = { rect.nw.lat, rect.nw.lon };
  *out++ = p1;
  return out;
}

その後、ベクトルや配列など、好みに合わせて使用​​できます。

std::vector<degPoint> points;
fillClosedRect(someRect, std::back_inserter(points));

degPoint points[5];
fillClosedRect(someRect, points);

より安全なコードを書きたい場合は、バックインサーターでベクター方式を使用できます。低レベルのコードで作業する場合は、ポインターを出力イテレーターとして使用できます。

于 2010-06-30T19:28:21.837 に答える
3

私はstd::vectororを使用します(これは本当に悪いので使用すべきではありません)いくつかの極端なケースでは、ポインタのようにプレーンな配列を使用することさえでき、そのような「キャスト」Point*の問題は発生しません。

于 2010-06-30T19:36:32.130 に答える
1

問題に対するあなたのフレーミング/考え方は正しくないと思います。通常、4 つの頂点を持つオブジェクトと 5 つの頂点を持つオブジェクトを具体的に入力する必要はありません。

ただし、タイプする必要がある場合はstruct、代わりに s を使用して具体的にタイプを定義できます。

struct Coord
{
    float lat, long ;
} ;

それで

struct Rectangle
{
    Coord points[ 4 ] ;
} ;

struct Pentagon
{
    Coord points[ 5 ] ;
} ;

それで、

// 4 pt version
void RectToPointArray(const degRect& rect, const Rectangle& rectangle ) ;

// 5 pt version
void RectToPointArray(const degRect& rect, const Pentagon& pent ) ;

ただし、この解決策は少し極端だと思います。s でstd::vector<Coord>期待どおりにサイズ (4 または 5 のいずれか) を確認するa は、assertうまくいくでしょう。

于 2010-06-30T21:31:32.140 に答える
1

このようなサイズのポインターではなく、標準のポインターを渡すだけではどうですか

void RectToPointArray(const degRect& rect, degPoint * points ) ;
于 2010-06-30T19:36:44.910 に答える
0

So my question is: is there a type-safe way of casting an array of one size to an array of a smaller size where the array type is the same?

No. I don't think the language allows you to do this at all: consider casting int[10] to int[5]. You can always get a pointer to it, however, but we can't 'trick' the compiler into thinking a fixed-sized has a different number of dimensions.

If you're not going to use std::vector or some other container which can properly identify the number of points inside at runtime and do this all conveniently in one function instead of two function overloads which get called based on the number of elements, rather than trying to do crazy casts, consider this at least as an improvement:

void RectToPointArray(const degRect& rect, degPoint* points, unsigned int size);

If you're set on working with arrays, you can still define a generic function like this:

template <class T, size_t N>
std::size_t array_size(const T(&/*array*/)[N])
{
    return N;
}

... and use that when calling RectToPointArray to pass the argument for 'size'. Then you have a size you can determine at runtime and it's easy enough to work with size - 1, or more appropriate for this case, just put a simple if statement to check if there are 5 elements or 4.

Later if you change your mind and use std::vector, Boost.Array, etc. you can still use this same old function without modifying it. It only requires that the data is contiguous and mutable. You can get fancy with this and apply very generic solutions that, say, only require forward iterators. Yet I don't think this problem is complicated enough to warrant such a solution: it'd be like using a cannon to kill a fly; fly swatter is okay.

If you're really set on the solution you have, then it's easy enough to do this:

template <size_t N>
void RectToPointArray(const degRect& rect, degPoint(&points)[N])
{
    assert(N >= 4 && "points requires at least 4 elements!");
    points[0].lat = rect.nw.lat; points[0].lon = rect.nw.lon;
    points[1].lat = rect.nw.lat; points[1].lon = rect.se.lon;
    points[2].lat = rect.se.lat; points[2].lon = rect.se.lon;
    points[3].lat = rect.se.lat; points[3].lon = rect.nw.lon;

    if (N >= 5)
        points[4].lat = rect.nw.lat; points[4].lon = rect.nw.lon;
}

Yeah, there is one unnecessary runtime check but trying to do it at compile time is probably analogous to taking things out of your glove compartment in an attempt to increase your car's fuel efficiency. With N being a compile-time constant expression, the compiler is likely to recognize that the condition is always false when N < 5 and just eliminate that whole section of code.

于 2010-06-30T23:55:26.900 に答える
0

次のように、関数テンプレートの特殊化を使用できると思います (最初の引数が無視され、関数名が f() などに置き換えられた単純化された例):

#include <iostream>
using namespace std;

class X
{
};

template<int sz, int n>
int f(X (&x)[sz])
{
    cout<<"process "<<n<<" entries in a "<<sz<<"-dimensional array"<<endl;
    int partial_result=f<sz,n-1>(x);
    cout<<"process last entry..."<<endl;

    return n;
}
//template specialization for sz=5 and n=4 (number of entries to process)
template<>
int f<5,4>(X (&x)[5])
{
    cout<<"process only the first "<<4<<" entries here..."<<endl;

    return 4;
}


int main(void)
{
    X u[5];

    int res=f<5,5>(u);
    return 0;
}

もちろん、n={0,1,2,3} のような他の (潜在的に危険な)特殊なケースに対処する必要があり、おそらく int の代わりに unsigned int を使用する方がよいでしょう。

于 2010-06-30T21:14:28.860 に答える