Дело в том, что указанный слева квалификатор
const
относится к правой половине спецификации типа до первого модификатора.
const char&
- ссылка на константный символ. Квалификатор -
const
, модификатор -
&
.
const char*
- указатель на память константного символа. Квалификатор -
const
, модификатор -
*
.
При этом, указанный справа квалификатор
const
относится ко всей части спецификации типа левее, включая все модификаторы.
char* const
- константный указатель на память символа.
const char* const
- константный указатель на память константного символа.
char* const *
- указатель на память константного указателя на память символа.
char& const
существовать не может, т.к. квалификаторы не применяется к ссылкам. Тут будет ошибка трансляции.
И при чем же здесь
constexpr
? Просто
constexpr
всегда относится только ко всей спецификации типа со всеми модификаторами.
const char*
- указатель на память константного символа.
constexpr char*
- константный указатель времени компиляции на память символа. Тут нет ошибки, память символа тут считается модифицируемой.
И если объект с типом
constexpr char*
получит характеристику ODR-used [
?], то после трансляции кода это будет уже объект с типом
char* const
. Вот так.
В то же время, строковые литералы имеют тип
const char[N]
, т.е. статически определенный массив константных символов. Такой тип можно привести только к типу
const char*
.
В результате, чтобы правильно определить константный указатель времени компиляции на память константного символа, нужно тип определить как
constexpr const char*
.
И
const
в этом месте никакого прямого отношения к
constexpr
не имеет.