Возврат указателя const к члену данных const и ключевому слову "auto". Немного смущен

Недавно я изучал С++, и только сегодня мы познакомились с константой const и понятием const. Чтобы лучше понять теорию, я пишу серию простых программ, чтобы убедиться, что я правильно понимаю концепцию. Я думал, что все понял, но потом, используя ключевое слово auto в одной из программ, я, кажется, немного застрял.

Чтобы проверить, что я понял, как работают константные указатели, я написал простую программу. Я не буду беспокоиться о публикации всего этого, потому что есть только две его части. У меня есть класс с членом данных const типа int:

const int tryToChangeMe;

Внутри этого класса у меня также есть функция-член, которая возвращает указатель const на указанную выше константу int:

const int* const MyClass::test()
{
    return &tryToChangeMe;
}

В моей основной функции я вызываю вышеупомянутую функцию, используя ключевое слово auto. Чтобы проверить, что то, что я думаю, что знаю о const, правильно, я затем пытаюсь переназначить переменную tryToChangeMe через указатель. Например:

auto temp = myClass.test();
*temp = 100;

Как я и ожидал, программа не будет компилироваться из-за ошибки, возникшей при попытке присвоить значение константной переменной. Тем не менее, я не просто вернул указатель на const, я вернул указатель на const (по крайней мере, то, что, как я думал, я сделал). Поэтому, чтобы проверить это, я попытался переназначить указатель на новый адрес памяти, вполне уверен, что я получаю аналогичную ошибку компиляции:

temp = new int;

Но довольно запутанно программа скомпилирована без каких-либо проблем. Пройдя через отладчик, он обнаружил, что достаточно уверен, указатель терял свой первоначальный адрес и был назначен совершенно новым. Удивляясь, что происходит, я просто случайно удалил ключевое слово auto и заменил его переменной полного типа:

const int* const temp = myClass.test();

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

Итак, после всего этого, я думаю, мой вопрос: почему? Почему ключевое слово auto позволяет вам обойти квалификатор const указателей? Я сделал что-то не так?

Кстати, я не уверен, имеет ли это значение, но я использую предварительный просмотр Visual Studio 2015

Ответы

Ответ 1

Как уже упоминалось, auto игнорирует cv-квалификаторы верхнего уровня. Прочтите эту статью, чтобы узнать, как работают auto и decltype.

Теперь, даже если auto не игнорирует const, в вашем случае temp все равно не будет const, поскольку верхние уровни cv-квалификаторов при обратных типах игнорируется, если возвращаемый тип имеет тип неклассов.

g++ даже вызывает следующее предупреждение с -Wextra

предупреждение: идентификаторы типов игнорируются в функции return type [-Wignored-qualifiers]

Это можно продемонстрировать с помощью С++ 14 decltype(auto). В отличие от auto, decltype(auto) не отбрасывает ссылки и квалификаторы cv верхнего уровня. Если вы измените свой пример, добавив следующие строки, код все равно будет компилироваться, доказывая, что temp не является указателем const.

decltype(auto) temp = myClass.test();
static_assert(std::is_same<const int*, decltype(temp)>{}, "");

С другой стороны, если test() возвращает объект типа класса с cv-квалификатором верхнего уровня, то auto будет отбрасывать const, но decltype(auto) не будет.

Живая демонстрация

Ответ 2

Причина в том, что переменные auto не по умолчанию const. Тот факт, что вы возвращаете значение const, не означает, что он должен быть назначен переменной const; значение копируется после всех (хотя это значение является указателем). Вы можете легко попробовать с явной спецификацией типа. Значение, сохраненное в myClass, не будет изменено при изменении переменной temp, а цель-указатель все еще const, поэтому константа по-прежнему соблюдается.

Ответ 3

Когда вы пишете

auto temp = rhs;
Вывод типа

работает следующим образом:

  • если rhs является ссылкой, тогда ссылка игнорируется

  • верхние уровни cv (const-volatile) -qualifiers rhs также игнорируются (они не игнорируются, однако, если вы делаете auto& temp = rhs;, в этом случае шаблон компилятора соответствует типу)

В вашем случае тип правой стороны

const int* const
           ^^^^^
           top-level cv qualifier

то есть. const указатель на const - int. Указатель похож на любую другую переменную, поэтому const -ness будет отбрасываться (технически, квалификатор cv верхнего уровня const и отбрасывается), поэтому вы получаете вывод temp а

const int*

то есть. указатель не const на const - int, поэтому его можно повторно назначить. Если вы хотите принудительно выполнить const -ness, тогда вам нужно объявить левую сторону как

const auto temp = myClass.test();
^^^^^
need this
Скотт Майерс отличное введение к теме (также доступно на его Эффективный современный С++, статьи 1 и 2, свободно просматриваемые здесь), в которых он объясняет, как работает тип вывода template, Как только вы это понимаете, понимание auto является ветерок, так как действительно auto тип дедукции очень близко имитирует систему вычитания типа шаблона (за исключением std::initializer_list<>).

ИЗМЕНИТЬ

Существует дополнительное правило для

auto&& temp = rhs;

но чтобы понять это, вам нужно понять как работают перенаправления (универсальные) ссылки и как работает свертывание ссылок.

Ответ 4

Я предоставил официальное объяснение из Стандарта для этого факта для пользователей, использующих стандартные ссылки:

Раздел N4296::7.1.6.4/7 [dcl.spec.auto]

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

Теперь аргумент шаблона N4296::14.8.2/3 [temp.deduct]:

[...] параметры параметров параметров параметров, описанные в 8.3.5, являются выполняется.

И наконец N4296::8.3.5/5 [dcl.fct]

После определения типа каждого параметра любой параметр типа "массив T" или "возвращающая функцию T" настраивается как "указатель на T" или "указатель на функцию, возвращающую T", соответственно. список типов параметров, любые cv-квалификаторы верхнего уровня, изменяющие тип параметра удаляется при формировании типа функции.

Короче говоря, да, CV-квалификаторы просто игнорируются в этом случае.