Ответ 1
C и С++ в этом отношении различны. У меня нет ответа на вопрос, почему С++ более щедрый, кроме того, что поведение С++ кажется мне правильным.
C просто не допускает косвенного преобразования const
. Это консервативное, простое в использовании ограничение, с неудачным последствием того, что вы не можете предоставить char*[]
функции, ожидающей char const* const*
. Ограничение находится в разделе 6.3.2.3, параграф 2, и это просто не рекурсивно:
Для любого квалификатора
q
указатель на не-t23 > -qualified тип может быть преобразован в указатель наq
-qualified версию типа; значения, хранящиеся в исходных и преобразованных указателях, сравниваются равными.
С++ допускает преобразования в соответствии с несколько сложной формулировкой в разделе 4.4 [conv.qual], абзац 3. Разрешено преобразовывать
T cvn Pn-1cvn-1 … P1cv1 P0cv0
→
T cv'n Pn-1cv'n-1 … P1cv'1 P0cv'0
(где T
- тип; P1…Pn
- конструкторы типа указателя/массива, и каждый cv0…cvn
является, возможно, пустым подмножеством const
и volatile
)
при условии, что:
-
Для каждого
k > 0
,cvk
является подмножествомcv'k
(поэтому вы не можете удалитьconst
илиvolatile
) и -
Если
cvk
иcv'k
отличаются для некоторогоk > 0
, все следующиеcv'i>k
включаютconst
.
В реальном стандарте это выражение отменяется; Я помещаю его в порядок декларации, тогда как в стандарте он находится в порядке применения конструкторов указателя/массива. Однако я не изменил направление нумерации, поэтому их пронумеровали справа налево. Я также отказался от некоторых деталей - например, не обязательно, чтобы два T
были идентичными, но я думаю, что это дает представление о намерении.
Объяснение первого ограничения достаточно очевидно. Второе ограничение предотвращает проблему, описанную в FAQ C, где указатель const
может быть сохранен в объект-указатель не const
, а затем впоследствии используется для мутаций объекта const
, на который он указывает.
Суть в том, что в С++ ваш прототип const char *const * param
будет работать с аргументами типа char**
, const char**
или даже char*const*
, но на C только последний будет работать без предупреждения, и это является наименее полезным. Единственное обходное решение, которое я знаю (кроме переключения на С++), - это игнорировать предупреждение.
За что стоит отметить в разделе раздел Обоснование спецификации Posix интерфейсов exec*
об этой проблеме для этих прототипов и обходной путь, выбранный Posix, который должен использовать char*[]
в качестве прототипа и текстовое примечание, что они являются постоянными: (выделено мной)
Утверждение о константах
argv[]
иenvp[]
включено, чтобы сделать явным для будущих писателей языковых привязок, что эти объекты полностью постоянные. Из-за ограничения стандарта ISO C невозможно утверждать эту идею в стандарте C. Указание двух уровнейconst
-qualification для параметровargv[]
иenvp[]
для Функции exec могут казаться естественным выбором, учитывая, что эти функции не изменяют ни массив указателей, ни символы, на которые указывает функция, но это будет запрещать существующий правильный код. Вместо этого только массив указателей отмечен как константа.
Там есть полезная таблица совместимости после этого параграфа, которую я не цитировал из-за ограничений форматирования этого сайта.