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

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

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

const int tryToChangeMe;

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

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

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

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

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

temp = new int;

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

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

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

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

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


person Pete    schedule 14.05.2015    source источник
comment
Эта статья Херба Саттера может помочь вам понять: herbsutter.com/2013/06/07/   -  person Tas    schedule 14.05.2015
comment
Спасибо. Теперь, когда я знаю о константах верхнего уровня, все становится более ясным.   -  person Pete    schedule 14.05.2015


Ответы (4)


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

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

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

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

Это можно продемонстрировать, используя decltype(auto) в C++14. В отличие от 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) — нет.

Демо

person Praetorian    schedule 14.05.2015

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

person StenSoft    schedule 14.05.2015
comment
Итак, если я правильно понимаю, значит ли это, что возвращать указатель const по существу бессмысленно? (не каламбур) - person Pete; 14.05.2015
comment
@Pete, если вам нужен указатель const, объявите его с помощью const auto - person vsoftco; 14.05.2015
comment
@vsoftco Да, теперь я это понимаю. Но если константность по-прежнему соблюдается без const auto, то в этом нет реальной необходимости. Правильный? - person Pete; 14.05.2015
comment
@Pete, проблема в том, что const-ность не соблюдается auto, она отбрасывается при выполнении auto temp. Квалификаторы cv не отбрасываются всякий раз, когда вы делаете auto& temp. В последнем случае компилятор выполняет сопоставление с образцом. - person vsoftco; 14.05.2015
comment
@vsoftco Копирование значения const не нарушает константность. Я думаю, вы имели в виду, что константность не сохраняется. - person StenSoft; 14.05.2015
comment
@StenSoft да, извините за небрежный язык. Я имел в виду, что const-ность указателя не соблюдается. Данные, на которые он указывает, действительно остаются const. Здесь больше всего поможет typedef. - person vsoftco; 14.05.2015

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

auto temp = rhs;

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

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

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

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

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

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

const int*

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

const auto temp = myClass.test();
^^^^^
need this

У Скотта Мейерса есть отличное введение в эту тему (также доступно на его < книга href="https://rads.stackoverflow.com/amzn/click/com/1491903996" rel="nofollow noreferrer" rel="nofollow noreferrer">Effective Modern C++ , пункты 1 и 2 можно бесплатно просмотреть здесь), в котором он объясняет, как работает дедукция типа template. Как только вы поймете это, понимание auto станет легким делом, поскольку на самом деле дедукция типа auto очень точно имитирует систему вывода шаблонного типа (за заметным исключением std::initializer_list<>).

ИЗМЕНИТЬ

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

auto&& temp = rhs;

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

person vsoftco    schedule 14.05.2015

Я приведу формальное объяснение этого факта из Стандарта для поиска по стандартным ссылкам.:

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

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

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

[...] выполняются корректировки типа функционального параметра, описанные в 8.3.5.

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

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

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

person Community    schedule 14.05.2015