C ++转换为更小的数组

时间:2010-06-30 19:22:48

标签: c++ arrays reinterpret-cast

这是一个关于C ++语言各种怪癖的有趣问题。我有一对函数,它们应该用矩形的角填充一系列点。它有两个重载:一个需要Point[5],另一个需要Point[4]。 5点版本是指一个封闭的多边形,而4点版本是指你想要4个角落的时期。

显然这里有一些重复工作,所以我希望能够使用4点版本来填充5点版本的前4个点,所以我不会复制那个代码。 (并不是说复制很多,但每当我复制和粘贴代码时,我都会有可怕的过敏反应,我想避免这种情况。)

问题是,C ++似乎并不关心将T[m]转换为T[n] n < m的想法。 static_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]传递一个数组的引用点是为了让我们至少可以确定调用者正在传递一个正确的“out参数”。

6 个答案:

答案 0 :(得分:4)

我认为通过重载来实现这一点并不是一个好主意。函数的名称不会告诉调用者是否要填充打开的数组。如果调用者只有一个指针并希望填充坐标(假设他想在不同的偏移处填充多个矩形成为更大数组的一部分)该怎么办?

我会用两个函数来做这个,让它们指点。大小不是指针类型的一部分

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

如果要编写更安全的代码,可以使用带有后插入器的向量方式,如果使用低级代码,则可以使用指针作为输出迭代器。

答案 1 :(得分:3)

我会使用std::vector(这是非常糟糕的,不应该使用)在某些极端情况下你甚至可以通过像Point*这样的指针使用普通数组然后你不应该有这样的“cast”麻烦。

答案 2 :(得分:1)

为什么不直接传递一个标准指针,而不是像这样的

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

答案 3 :(得分:1)

我不认为你的框架/思考问题是正确的。您通常不需要具体地键入具有4个顶点的对象与具有5的对象的对象。

但是如果你必须输入它,那么你可以使用struct来具体地定义类型。

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

我认为这个解决方案有点极端,而std::vector<Coord>可以检查其大小(为4或5)与assert s一样,也可以。

答案 4 :(得分: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},你可能最好不要使用unsigned int而不是整数。

答案 5 :(得分:0)

  

所以我的问题是:有没有   类型安全的方法来构建一个数组   一个尺寸到一个较小尺寸的数组   数组类型是一样的吗?

没有。我认为语言根本不允许你这样做:考虑将int [10]转换为int [5]。但是,总是可以获得指向它的指针,但是我们不能“欺骗”编译器认为固定大小的维度有不同的维度。

如果您不打算使用std :: vector或其他能够在运行时正确识别内部点数的容器,并且可以在一个函数中方便地执行此操作,而不是根据数字调用两个函数重载对于元素,而不是试图做疯狂的演员,至少考虑这是一个改进:

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

如果你已经开始使用数组,你仍然可以定义这样的通用函数:

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

...并在调用RectToPointArray传递'size'参数时使用它。然后你有一个你可以在运行时确定的大小,并且很容易使用size - 1,或者更适合这种情况,只需输入一个简单的if语句来检查是否有5个元素或4个。

稍后如果你改变主意并使用std :: vector,Boost.Array等,你仍然可以使用这个相同的旧函数而不修改它。它只要求数据是连续的和可变的。您可以对此有所了解并应用非常通用的解决方案,例如,只需要前向迭代器。然而,我不认为这个问题足够复杂,无法保证这样的解决方案:它就像使用大炮杀死苍蝇一样;苍蝇拍是好的。

如果你真的开始使用你的解决方案,那么这很容易做到:

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

是的,有一个不必要的运行时检查但是在编译时尝试这样做可能类似于从手套箱中拿出东西以试图提高汽车的燃油效率。当N是编译时常量表达式时,编译器可能认识到当N