Почему const char * const & = "hello" компилируется?

Я читаю фрагмент кода из книги и нахожу это:

const char* const & a = "hello"; //can compile 
const char*& a = "hello"; //cannot

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

const char* const &, ссылка на const pointer, указатель указывает на const char.

const char*&, ссылка на pointer, указатель указывает на const char.

Итак, почему добавление дополнительной const, указывающее, что указатель является const, позволяет компилировать?

Ответ 1

Он по существу придерживается этой формулы

T const & a = something_convertible_to_T;

Где T - const char*. В первом случае временный указатель может быть материализован, назначен адрес литерала, а затем привязан к ссылке. Во втором случае, поскольку ссылка lvalue не const, это не может произойти. Другой пример более того же

const char* && a = "hello"; // rvalue ref makes a no into a yes.

Теперь временный указатель привязан к ссылке rvalue.

Ответ 2

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

Синтаксически синтаксически. В обеих строках мы определяем ссылку a, и в обоих мы будем иметь материализацию временного указателя, который берет адрес строкового литерала. Единственное отличие между ними состоит в том, что второй const появляется только здесь:

const char* const & a = "hello";

и не здесь:

const char*& a = "hello";

Этот 2-й const означает, что объект, на который делается ссылка здесь, указатель в этом случае сам по себе является const, так как в нем его нельзя изменить с использованием этой ссылки.

Следовательно, поскольку тип этого строкового литерала является const char[6] (а не const char * например), наша ссылка lvalue на тип const char* во второй строке не может привязываться к нему, но ссылка в первой строке, являясь ссылкой на тип const char* const. Зачем? Из-за правил ссылочной инициализации:

(5) Ссылка на тип "cv1 T1" инициализируется выражением типа "cv2 T2" следующим образом:

  • (5.1) Если ссылка является ссылкой lvalue и выражением инициализатора

    • (5.1.1) является lvalue (но не является битовым полем), а "cv1 T1" является ссылочным-совместимым с "cv2 T2", [...]
    • (5.1.2) имеет тип класса (т.е. T2 - тип класса) [...]

Оба выражения являются lvalues, но наш "cv1 T1" не является ссылочным-совместимым с нашими "cv2 T2" и "T2" не является типом класса.

  • (5.2) В противном случае, если ссылка является ссылкой lvalue на тип, который не является const-квалифицированным или является нестабильным, программа плохо сформирована

Ссылка действительно не является const-свойством: наш "T1" представляет собой const char*, который является указателем на const, в отличие от указателя const. Фактический тип здесь - тип указателя, так что это важно.

Ошибка Clang для второй строки, прочитанная с учетом этого, говорит нам именно об этом:

error: non-const lvalue reference to type 'const char *' cannot bind to a value of unrelated type 'const char [6]'
    const char*& a = "hello";
                 ^    ~~~~~~~

Часть о том, что ссылка не const const равна точно ¶5.2 - lvalue в нашем случае является указателем на const, но сама по себе не const! В отличие от первой строки. Часть о привязке к несвязанному типу точно равна ¶5.1 - наш const char* несовместим с RHS, являющимся const char[6], или const char* const после преобразования массива в указатель.

По этой точной причине или ее отсутствию это может быть без ошибок:

char* const & a = "hello";

ISO С++ 11 в стороне, компилятор позволяет этому пройти (не то, что он должен, поскольку строковый литерал является "const char 6 ", и мы не должны отбрасывать этот первый const), поскольку ссылка теперь const с относится к его объекту, указателю.

Еще одна интересная вещь: ссылка rvalue const char* && a (no "2nd const ") может привязываться к временному указателю, который был реализован из строкового литерала, как предоставил @StoryTeller. Это почему? Из-за правил преобразования массива в указатель:

Lvalue или rvalue типа "массив NT" или "массив неизвестной границы T" можно преобразовать в prvalue типа "указатель на T".

Здесь нет упоминания о const или другой cv-qualification, но это стоит только до тех пор, пока мы инициализируем rvalue ref.