Есть ли разница между 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 шт):
Разница только в том, что показывает decltype(функция)
, больше никакой разницы нет.
Причем в decltype(функция())
тоже нет разницы, там даже для const char *const foo()
выводится просто const char *
.
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();
делают разные вещи. Но второе почти всегда вредно, потому что не дает переместить возвращаемое значение.
Когда дело касается возвращаемого значения возникает важный ньюанс: категория выражения будет 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
посредством вызова методов класса.