Есть ли разница между const char* foo() и const char* const foo()?

Я вроде бы понимаю разницу между const char* и const char* const. В одном случае мы можем менять указатель, а в другом нет:

const char* s = "World";
s = "New world"; // Допустимо
const char* const s2 = "World";
s2 = "New world"; // Ошибка

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


Ответы (2 шт):

Автор решения: HolyBlackCat

Разница только в том, что показывает decltype(функция), больше никакой разницы нет.

Причем в decltype(функция()) тоже нет разницы, там даже для const char *const foo() выводится просто const char *.

[expr.type]/2:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

Но, как написано в цитате, это все верно только если возвращаемый тип - не класс. (И не массив, но их нельзя вернуть из функции по значению, поэтому игнорируем.)

Для классов T foo(); и const T foo(); делают разные вещи. Но второе почти всегда вредно, потому что не дает переместить возвращаемое значение.

→ Ссылка
Автор решения: user7860670

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

int foo() { return 0; }
int main()
{
    // Оператор = требует lvalue с левой стороны, а если бы прокатывала rvalue,
    // то получалось бы просто игнорирование возвращенного значения.
    foo() = 3; // error
}
int foo() { return 0; }
int main()
{
    // Взятие адреса тоже требует lvalue, а если бы прокатывала rvalue,
    // то нет смысла записывать что-то во временное значение чтобы сразу потерять.
    &(foo()); // error
}
int foo() { return 0; }
void ddd(int &)
{
    ...
}
int main()
{
    // Биндинг ссылки на изменяемый объект для rvalue не производится.
    ddd(foo()); // error
}
int foo() { return 0; }
void ddd(int const &)
{
    ...
}
int main()
{
    // Биндинг ссылки на не изменяемый объект работает и для rvalue.
    ddd(foo()); // OK
}
int foo() { return 0; }
void ddd(int &&)
{
    ...
}
int main()
{
    // Этот вызов работает, однако семантика перемещения имеет смысл только
    // для объектов класса, где можно добиться разного поведения за счет разных 
    // перегрузок операций для rvalue и lvalue.
    ddd(foo()); // OK
}
int foo() { return 0; }
void ddd(int &&)
{
    ...
}
void ddd(int const &&)
{
    ...
}
int main()
{
    // В этом случае между возвратом int и int const была бы разница,
    // однако нет смысла в перегрузке int const && вместо int const &.
    ddd(foo()); // OK
}

Для не примитивных типов изменение возможно и для rvalue посредством вызова методов класса.

→ Ссылка