Запрет на использование массивов с передачей "по значению" в качестве типов параметров при объявлении функций.

Никита Колотов
Никита Колотов

С давних времен в С и С++ имеется специальное правило, по которому параметры функций с типом массив T на деле получают тип указатель на T: [dcl.fct] 11.3.5 Functions 5 ... After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T”. Это правило выглядит неконсистентным даже на фоне прочих языковых неурядиц. Однако главное в том, что его использование приводит к опасному сочетанию "подразумевания" наличия у такого параметра свойств массива с о стороны разработчика с "попустительством" со стороны компилятора для которого это просто указатель. На практике это часто выливается в целый букет дефектов, как то:

1 закладывание на то, что массив действительно имеет указанный размер:

void foo(int items[10])
{
    for(size_t item_index{}; item_index < 10; ++item_index) // fail
    {
        cout << items[item_index];
    }
}
...
int items[5];
foo(items);

2 попытки получить размер массива "как обычно":

void foo(int items[100])
{
    size_t items_count{sizeof(items) / sizeof(items[0])}; // fail
    for(size_t item_index{}; item_index < items_count; ++item_index)
    {
        cout << items[item_index];
    }
}

3 игнорирование опциональности такого параметра:

void foo(int items[10])
{
    cout << items[0]; // fail
}
...
foo(nullptr);

4 промахи с вызовом перегрузок:

void foo(int poks[10], int raks[10])
{
    swap(poks, raks); // oops
}

5 неочевидная невозможность задать const квалификатор для такого параметра чтобы соблюсти const-correctness:

void foo(int const items[10])
{
    items = nullptr; // this is fine
}

В С возникающие проблемы по большей части такие же, хотя там может добавится дополнительная головная боль с VLA и использоваться несовместимый (и бесполезный) синтаксис со `static` размером `void foo(int items[static 10])`.

При этом я затрудняюсь найти хоть какие-то полезные применения для этого правила. Мне представляется, что все возможные сценарии можно без проблем реализовать переходом на передачу массива по ссылке / по указателю, либо на передачу просто сырого указателя (+ количество элементов),  либо на передачу array_view. Соответственно я предлагаю это правило заменить на прямой запрет таких действий: If function has parameter of type “array of T” the program is ill-formed.

 

6
рейтинг
7 комментариев
Andrey

Эта идея кажется неплохим кандидатом для C++ в новой эпохе, если эпохи будут когда-нибудь приняты. Есть ряд вопросов.

1. Почему компиляторы не дают warnings на void test(int arr[10]);? У них есть какие-то разумные причины -- если да, то какие, если нет, то почему бы не реализовать это для начала как warning?

2. Как это должно работать в dependent коде?

Case 1:

template<typename T>
void test(int arr[sizeof(T) - sizeof(int)]);

Case 2: 

template<typename T>
void test(typename T::type);

Если T::type это массив, он должен декэиться или давать SFINAE-error или hard error?

3. Стоит ли также запретить function type как параметр функии?

4. Не жалко ли совместимости с C?

Andrey
Никита Колотов

Andrey, 1. По аналогии с удалением триграфов, тут тоже вполне будет уместно разделить на две стадии - сначала объявление такого синтаксиса устаревшим, а затем уже полноценный запрет. Существующие компиляторы / статические анализаторы уже могут выдавать некоторые диагностики для подобных случаев, например warning c26485 в vc++ или cppcoreguidelines-pro-bounds-array-to-pointer-decay в clang-tidy.

2. Сase 1 - однозначно program ill-formed. А case 2 - SFINAE, сигнатура этой перегрузки просто не подходит, хотя среди прочих может найтись и подходящая.

Кстати с устранением этого правила вывода параметров шаблонов немного упростятся, особый пункт [temp.deduct.type] Except for reference and pointer types, a major array bound is not part of a function parameter type and cannot be deduced from an argument станет ненужным.

3. Да, я хотел создать для этого отдельное предложение. Хотя с указателями на функции проблем меньше.

4. Совместимость с С в этом плане и так только частичная: синтаксис с VLA и со `static` в С++ не поддерживается. А вообще было бы хорошо убрать аналогичное правило и из С.

Никита Колотов
Andrey

Я привел Case 1, потому что сейчас выражение в размере массива участвует в SFINAE: https://gcc.godbolt.org/z/jg--nU (правда не для GCC).

Не хотите все-таки начать с function type, я понимаю, что пользы от этого меньше, но с другой стороны это намного менее constroversial, т.е. можно будет посмотреть как в целом комитет по стандартизации реагирует на такого рода предложения, ломающие обратную совместимость?

Andrey
Никита Колотов

Andrey, Мне представляется, что отношение комитета к такого рода предложениям известно, и оно в целом негативное. Вроде как даже против удаления триграфов были серьезные возражения, хотя они имели крайне малое распространнение, а содержащий их код потребовал бы только тривиального рефакторинга. А тут пользователям функций, принимающих массивы, придется основательно перебирать свой код.

Никита Колотов
Andrey

Никита Колотов, кстати запрет decay для function type поможет выдавать более осмысленные сообщение об ошибках в случае ""the most vexing parse".

int x(int()); // ошибка тут, а не там где мы пытаемся работать с x как с переменной типа int.
Andrey
Никита Колотов

По-каким-то причинам редактировать предложения нелязя, так что дополню тут. К п.5 можно придумать более вопиющий пример:

using items_t = int const [10];

void test(items_t const items) // const ignored
{
    items = 0; // wat
}
Никита Колотов
Никита Колотов

Дополнение: Так как размер массива и само его присутствие не входят в сигнатуру функции, то между различными предварительными объявлениями функции и ее определением могут быть радикальные расхождения, создавая неразбериху. Например:

void foo(int * arr);
void foo(int arr[2]);
void foo(int arr[142]);
void foo(int arr[10]){}
Никита Колотов
Обновлено 
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения