Я только что наткнулся на std::as_const и был удивлен выводом последней строки в следующем фрагменте:
#include <cstdio>
#include <utility>
struct S {
void foo() { std::puts("foo: non const"); }
void foo() const { std::puts("foo: const"); }
};
int main() {
S s;
s.foo(); // foo: non const
std::as_const(s).foo(); // foo: const
auto* s_ptr = &s;
s_ptr->foo(); // foo: non const
std::as_const(s_ptr)->foo(); // foo: non const (?)
}
Глядя на документацию, я понимаю, почему вызывается не-const перегрузка foo:
std::as_const(s_ptr) возвращает S* const&, то есть ссылку на константу
указатель на неконстанту S вместо S const*, то есть указатель на константу S, как я бы
ожидали.
Итак, мой вопрос: почему стандарт не предоставляет перегрузку std::as_const для типов указателей? Например. что-то вроде:
template <class T>
constexpr std::add_const_t<T>* as_const(T* t) noexcept {
return t;
}
Редактировать: одним из мотивов для std::as_const в статье P0007R1 является выбор перегрузки функции без необходимости прибегать к const_cast. P0007R1 предоставляет этот пример:
int processEmployees( std::vector< Employee > &employeeList );
bool processEmployees( const std::vector< Employee > &employeeList );
A larger project often needs to call functions, like
processEmployees, and selecting among specificconstor non-constoverloads. [...]
Вот почему я был как-то удивлен, что это не помогает в разрешении перегрузки, когда применяется к указателю в коде, подобном тому, который я опубликовал, а также в:
std::as_const(this)->foo();
ни при выборе последней из следующих перегрузок:
int processEmployees( std::vector< Employee > *employeeList );
bool processEmployees( const std::vector< Employee > *employeeList );
std::as_const, и это не должен.
Что вы предлагаете int*** p; std::as_const(p) делать? Здесь есть четыре разных места, где можно представить const.
Я спрашивая с вопросом. Я ничего не предложение.
Что ж, вы ожидали, что as_const<int*> вернётся int const*, а не int* const. Чего бы вы ожидали от as_const<int***>?
@IgorTandetnik Я ожидал, что std::as_const позволит мне выбрать void f(T const*) вместо void f(T*) так же, как он позволяет мне выбрать void f(T const&) вместо void f(T&). Итак, я ожидал, что std::as_const<int***> вернет мне int** const*.
std::as_const(*this).foo();А как насчет unique_ptr и shared_ptr? А как насчет итераторов (всех, включая еще не написанные)? Вы ожидаете, что там произойдет что-то особенное?
Что не так с std::as_const(*s_ptr).foo();?





Цель std::as_const состоит в том, чтобы иметь возможность ссылаться на не-const lvalue как на const lvalue, чтобы его нельзя было изменить в контексте, в котором оно используется. Другими словами, std::as_const(x) должно быть сокращением для написания
const auto& y = x;
а затем с помощью y.
Он уже делает это хорошо, поэтому нет необходимости в специальном поведении для указателей.
А вот простой пример, когда предлагаемая дополнительная перегрузка будет иметь серьезные негативные последствия:
std::vector<int> vec = /*...*/;
for(auto it = std::begin(vec); it != std::end(vec); it++)
func(std::as_const(it));
Цель здесь состоит в том, чтобы убедиться, что функция func не может изменить it, поскольку ответственность за итерацию по вектору лежит на цикле for. Если func просто принимает итератор по значению или ссылке const, то std::as_const не является строго обязательным, но в любом случае имеет смысл в качестве меры безопасности или потому, что существует несколько перегрузок func, некоторые из которых изменяют свой аргумент.
auto вот какой-то тип итератора. Это мог будет указателем. Или это может быть тип класса. С предложенной вами перегрузкой as_const это сломается в зависимости от того, как реализован итератор std::vector.
std::as_const(it) должен сказать, что it не может быть изменен при таком использовании. Он не должен ничего говорить о том, можно ли модифицировать объект, на который ссылается it. Это не его цель. Конечно, было бы целесообразно добавить функцию, которая делает объект упоминается немодифицируемым. Но у него должно быть другое имя, и вы, вероятно, захотите реализовать его для произвольных итераторов, а не конкретно для указателей. По сути, адаптер итератора к const-итератору.
Предлагаемая разница в поведении была бы катастрофой для шаблонных функций.