Бросок C++ к массиву меньшего размера

Вот интересный вопрос о различных причудах языка C++. У меня есть пара функций, которые, как предполагается, заполняют массив точек с углами прямоугольника. Существует две перегрузки для него: каждый берет a Point[5], другие взятия a Point[4]. Версия с 5 точками относится к закрытому полигону, тогда как версия с 4 точками - когда Вы просто хотите эти 4 угла, период.

Очевидно, существует некоторое дублирование работы здесь, таким образом, я хотел бы смочь использовать версию с 4 точками для заполнения первых 4 точек версии с 5 точками, таким образом, я не копирую тот код. (Не то, чтобы это очень для дублирования, но у меня есть ужасные аллергические реакции каждый раз, когда я копирую и вставляю код, и я хотел бы избежать этого.)

Вещь, C++, кажется, не заботится об идее преобразовать a T[m] к a 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], который - точка передачи массива ссылкой то, так, чтобы мы могли быть по крайней мере неопределенно уверены, что вызывающая сторона является передающей в корректном "параметр".

8
задан Sean Edwards 30 June 2010 в 19:41
поделиться

6 ответов

Я не думаю, что это хорошая идея делать это путем перегрузки. Имя функции не сообщает вызывающей стороне, собирается она заполнять открытый массив или нет. А что, если у вызывающего есть только указатель и он хочет заполнить координаты (допустим, он хочет заполнить несколько прямоугольников, чтобы они были частью большего массива с разными смещениями)?

Я бы сделал это двумя функциями и позволил бы им взять указатели. Размер не является частью типа указателя

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

Если вы хотите написать более безопасный код, вы можете использовать векторный способ с обратными вставками, а если вы работаете с кодом более низкого уровня, вы можете использовать указатель в качестве итератора вывода.

4
ответ дан 5 December 2019 в 18:56
поделиться

Я полагаю, вы могли бы использовать специализацию шаблона функции, как это (упрощенный пример, где первый аргумент был проигнорирован, а имя функции было заменено на 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}, и вам, вероятно, лучше использовать беззнаковые целые числа вместо целых.

0
ответ дан 5 December 2019 в 18:56
поделиться

Я бы использовал std :: vector или (это действительно плохо и не должно использоваться) в некоторых крайних случаях вы даже можете использовать простой массивы через указатель типа Point * и тогда у вас не должно возникнуть таких "кастингов" проблем.

3
ответ дан 5 December 2019 в 18:56
поделиться

Почему бы вам просто не передать стандартный указатель вместо указателя размера, как этот

void RectToPointArray(const degRect& rect, degPoint * points ) ;
1
ответ дан 5 December 2019 в 18:56
поделиться

Я не думаю, что вы правильно формулируете / думаете о проблеме. Обычно вам не нужно конкретно вводить объект с 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 ) ;

Я думаю, что это решение немного экстремально, и std :: vector , который вы проверяете, его размер (4 или 5) как и ожидалось с assert s, подойдет.

1
ответ дан 5 December 2019 в 18:56
поделиться

Итак, мой вопрос: существует ли безопасный для типов способ приведения массива одного размера в массив меньшего размера где тип массива одинаков?

Нет. Я не думаю, что язык вообще позволяет это сделать: рассмотрим приведение 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 < 5, и просто удалит весь этот участок кода.

0
ответ дан 5 December 2019 в 18:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: