Верхний уровень const не влияет на сигнатуру функции

Из C ++ Primer 5th Edition говорится:

int f(int){ /* can write to parameter */} int f(const int){ /* cannot write to parameter */} 

Обе функции неотличимы . Но, как вы знаете, обе функции действительно отличаются тем, как они могут обновлять свои параметры.

Может кто-то объясняет мне?


РЕДАКТИРОВАТЬ
Думаю, я не очень хорошо истолковал свой вопрос. Меня действительно волнует, почему C ++ не допускает одновременную работу этих двух функций как разную функцию, так как они действительно отличаются от того, «может ли параметр быть написан или нет». Интуитивно это должно быть!


РЕДАКТИРОВАТЬ
Характер передачи по значению фактически передается путем совпадения значений аргумента с значениями параметров . Даже для ссылок и указателей, где ваши скопированные значения являются адресами . С точки зрения звонящего, передается ли const или non-const функции, не влияет на значения (и, конечно, типы), скопированные в параметры.
Различие между константой верхнего уровня и низким уровнем const имеет значение при копировании объектов. Более конкретно, const-level верхнего уровня (не в случае низкого уровня const ) игнорируется при копировании объектов, поскольку копирование не повлияет на скопированный объект. Не имеет значения, является ли объект, скопированный или скопированный, const или нет.
Таким образом, для вызывающего абонента их дифференциация не требуется. Вероятно, с точки зрения функции параметры const верхнего уровня не влияют на интерфейс и / или функциональность функции. Две функции фактически выполняют одно и то же. Зачем докучать две копии?

разрешить эти две функции одновременно как разную функцию, так как они действительно отличаются от того, «может ли параметр быть написан или нет». Интуитивно это должно быть!

Перегрузка функций основана на параметрах, предоставляемых вызывающим абонентом. Здесь верно, что вызывающий может предоставлять const или не const значение, но логически это не должно влиять на функциональность, предоставляемую вызываемой функцией. Рассматривать:

 f(3); int x = 1 + 2; f(x); 

Если f() делает разные вещи в каждой из этих ситуаций, это будет очень запутанным! Программист этого кода, вызывающий f() может иметь разумное ожидание одинакового поведения, свободно добавляя или удаляя переменные, которые передают параметры без его недействительности программы. Это безопасное, нормальное поведение является отправной точкой, которую вы хотите оправдать исключениями, и действительно есть одно – поведение может варьироваться, когда перегруженная функция ala:

 void f(const int&) { ... } void f(int&) { ... } 

Итак, я думаю, это то, что вы находите неинтуитивным: что C ++ обеспечивает более «безопасность» (принудительное согласованное поведение посредством поддержки только одной реализации) для не-ссылок, чем ссылки .

Причины, о которых я могу думать, это:

  • Поэтому, когда программист знает, что параметр non const& имеет более длительный срок службы, они могут выбрать оптимальную реализацию. Например, в приведенном ниже коде может быть быстрее вернуть ссылку на T член внутри F , но если F является временным (что может быть, если компилятор соответствует const F& ), тогда необходим возврат по значениям. Это все еще довольно опасно, поскольку вызывающий должен знать, что возвращаемая ссылка действительна только в том случае, если параметр находится вокруг.
     T f (const F &);
     T & f (F &);  // возвращаемый тип может быть const и если более уместно
  • распространение classификаторов типа const -ness через вызовы функций, как в:
     const T & f (const F &);
     T & f (F &);

Здесь некоторая (предположительно F членная) переменная типа T подвергается воздействию как const или не const основанная на const -ness параметра при вызове f() . Этот тип интерфейса можно выбрать, если вы хотите расширить class с помощью нечленов-функций (чтобы сохранить минимальный class или при написании шаблонов / algos, используемых на многих classах), но идея аналогична функции-члену const например vector::operator[]() , где вы хотите, чтобы v[0] = 3 разрешалось на не- const векторе, но не const .

Когда значения принимаются по значению, они выходят из области видимости при возврате функции, поэтому нет допустимого сценария, включающего возврат ссылки на часть параметра и необходимость распространения ее classификаторов.

Взламывание поведения, которое вы хотите

Учитывая правила для ссылок, вы можете использовать их для получения желаемого поведения – вам просто нужно быть осторожным, чтобы случайно не изменить параметр «не-константа-ссылка», поэтому может потребоваться применить такую ​​практику, как: неконстантные параметры:

 T f(F& x_ref) { F x = x_ref; // or const F is you won't modify it ...use x for safety... } 

Последствия перекомпиляции

Помимо вопроса о том, почему язык запрещает перегрузку, основанную на const -ness параметра значения, возникает вопрос, почему он не настаивает на согласованности константы в декларации и определении.

Для f(const int) / f(int) … если вы объявляете функцию в заголовочном файле, тогда лучше не включать const даже если это будет иметь более позднее определение в файле реализации. Это связано с тем, что во время обслуживания программист может захотеть удалить квалификатор … удаление его из заголовка может вызвать бессмысленную перекомпиляцию клиентского кода, поэтому лучше не настаивать на том, чтобы они хранились в синхронизации, – и действительно, поэтому компилятор не делает этого, t создает ошибку, если они отличаются. Если вы просто добавляете или удаляете const в определении функции, то это близко к реализации, где читатель кода может заботиться о константе при анализе поведения функции. Если у вас есть const в файле заголовка и реализации, тогда программист хочет сделать его const и забывает или решает не обновлять заголовок, чтобы избежать перекомпиляции клиента, тогда он более опасен, чем наоборот, насколько это возможно программист будет иметь версию const из заголовка при попытке проанализировать текущий код реализации, приводящий к неправильным рассуждениям о поведении функции. Все это очень тонкая проблема сохранения – это действительно актуально для коммерческого программирования, но это является основой руководства не использовать const в интерфейсе. Кроме того, это более кратким, чтобы опустить его из интерфейса, что лучше для клиентских программистов, читающих ваш API.

Поскольку для вызывающего нет никакой разницы, и нет четкого способа отличить вызов от функции с параметром const верхнего уровня и без него, правила языка игнорируют константы верхнего уровня. Это означает, что эти два

 void foo(const int); void foo(int); 

рассматриваются как одна и та же декларация. Если бы вы предоставили две реализации, вы получили бы ошибку с множественным определением.

Существует различие в определении функции с верхним уровнем const. В одном вы можете изменить свою копию параметра. В другом вы не можете. Вы можете увидеть его как деталь реализации. Для вызывающего нет никакой разницы.

 // declarations void foo(int); void bar(int); // definitions void foo(int n) { n++; std::cout << n << std::endl; } void bar(const int n) { n++; // ERROR! std::cout << n << std::endl; } 

Это аналогично следующему:

 void foo() { int = 42; n++; std::cout << n << std::endl; } void bar() { const int n = 42; n++; // ERROR! std::cout << n << std::endl; } 

В четвертом выпуске «Язык программирования C ++» Бьярне Страуструп пишет (§12.1.3):

К сожалению, для сохранения совместимости C, const игнорируется на самом высоком уровне типа аргумента. Например, это два объявления одной и той же функции:

 void f(int); void f(const int); 

Таким образом, кажется, что, обратившись к некоторым другим ответам, это правило C ++ не было выбрано из-за неразличимости двух функций или других подобных рассуждений, а вместо этого как менее оптимальное решение, ради совместимость.

Действительно, на языке программирования D возможно иметь эти две перегрузки. Тем не менее, в отличие от других ответов на этот вопрос, непреднамеренная перегрузка предпочтительна, если функция вызывается с помощью литерала:

 void f(int); void f(const int); f(42); // calls void f(int); 

Конечно, вы должны предоставить эквивалентную семантику для своих перегрузок, но это не относится к этому сценарию перегрузки, с почти неотличимыми функциями перегрузки.

Как видно из комментариев, внутри первой функции параметр может быть изменен, если он был назван. Это копия инт. Внутри второй функции любые изменения в параметре, который все еще является копией intall вызываемого, приведут к ошибке компиляции. Const – это promise, что вы не измените переменную.

Функция полезна только с точки зрения вызывающего абонента.

Поскольку для вызывающего нет никакой разницы, для этих двух функций нет никакой разницы.

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

Компилятор не различает эти две функции, их имена искажаются одинаково. Это приводит к ситуации, когда компилятор рассматривает эти два объявления как переопределение.

Отвечая на эту часть вашего вопроса:

Меня действительно волнует, почему C ++ не допускает одновременную работу этих двух функций как разную функцию, так как они действительно отличаются от того, «может ли параметр быть написан или нет». Интуитивно это должно быть!

Если вы думаете об этом немного больше, это вовсе не интуитивно – на самом деле, это не имеет большого смысла. Как и все остальные, вызывающий абонент никоим образом не влияет, когда functon принимает его параметр по стоимости, и ему тоже все равно.

Теперь давайте предположим, что нагрузка на перегрузку тоже работала на верхнем уровне const . Две декларации, подобные этой

 int foo(const int); int foo(int); 

объявит две разные функции. Одна из проблем заключалась в том, какие функции вызывают это выражение: foo(42) . Языковые правила могли бы сказать, что литералы являются const и что в этом случае будет вызвана «перегрузка» const. Но это наименьшая проблема. Программист, чувствующий себя достаточно злым, мог написать это:

 int foo(const int i) { return i*i; } int foo(int i) { return i*2; } 

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

  • Определить имена таблиц и столбцов в качестве аргументов в функции plpgsql?
  • как тестировать облачные функции для Firebase локально на ПК
  • Использование boost-streamа и нестатической функции classа
  • Как я могу создать функцию с обработчиком завершения в Swift?
  • Разделительная строка с разделителями-запятыми -> FUNCTION db.CHARINDEX не существует
  • В MATLAB, могу ли я иметь скрипт и определение функции в том же файле?
  • Есть ли способ использовать два оператора «...» в функции из R?
  • Что более эффективно: вернуть значение против Pass by reference?
  • Отправить форму при нажатии Enter с помощью AngularJS
  • Как сделать .lib-файл, когда есть .dll-файл и заголовочный файл
  • Перейти к определению функции в vim
  • Interesting Posts

    Почему реализация HashSet в Sun Java использует HashMap в качестве поддержки?

    Использование обратной косой черты для удаления символов в cmd.exe (например, команда runas)

    Как работает пропускная способность USB? Будет ли несколько устройств разделять ограничение 480 Мбит / с для USB 2.0?

    AngularJS: Предотrotation ошибки $ digest уже выполняется при вызове $ scope. $ Apply ()

    Окончательный список общих причин сбоев сегментации

    Spring Security с ролями и разрешениями

    Измените, что закрывает крышку, из командной строки?

    Почему обвалы настолько вредны?

    Ориентация изображения MFMailComposeViewController

    Что такое эквивалент async / await сервера ThreadPool?

    libavcodec.so: имеет перемещение текста

    Какие форматы графических файлов поддерживаются браузерами?

    Предотвратите модульные тесты в maven, но разрешите интеграционные тесты

    Преобразование xml в строку с помощью jQuery

    Создание уникальных случайных чисел в Java

    Давайте будем гением компьютера.