Const-вход с неконстантным выходом
У меня есть функция, которая пересекает дерево объектов и не изменяет ни один из объектов в дереве.
Функция выглядит примерно так:
static Node* findMatchingNode(const Node& root, const SomeFilterData& d);
struct Node {
Node* left;
Node* right;
};
Функция может возвращать корень или любой объект в дереве или ничего. Это очевидно, чем при данной декларации, мне нужно выполнить const_cast где-то, что запрещено в большинстве случаев.
Допустимо ли для функции гарантировать константу и в то же время разрешить кому-либо изменять ее выход?
ИЗМЕНИТЬ. Я не сказал этого ясно, моя функция действительно не модифицирует ни один из узлов в дереве, не создает новую Node, это чистую функцию. Я хотел бы всегда иметь квалификатор const
, чтобы ясно сказать всем, что функция ничего не меняет
ИЗМЕНИТЬ. Невысказанная проблема заключается в том, что нет законного способа выражения консистенции ввода во время выполнения функции (и только внутри функции) без принудительной установки выходной константы.
Ответы
Ответ 1
Ваш код не const-correct, он отключает const. Вот почему у вас есть проблема "требуемого" создания const. Не делайте этого:
static const Node* findMatchingNode(const Node& root, const SomeFilterData& d);
Вы можете указать, что вы можете вызвать эту функцию из другой функции, которая модифицирует узлы, и, следовательно, хочет получить неконстантный результат. Таким образом, это должна быть новая функция с совершенно другой сигнатурой:
static Node* findMatchingNode(Node& root, const SomeFilterData& d);
Вы можете указать, что у них одинаковые тела, и существует принцип СУХОЙ (DRY = Do not Repeat Yourself = Не имеет копируемого кода). Да, просто воспользуйтесь ярлыком здесь: const_cast. Я думаю, что это нормально в этом случае, потому что это только цель - это общий код, и он ясно говорит о том, что он не нарушает никаких принципов const-correctness.
//This function does not modify anything
static Node* findMatchingNode(Node& root, const SomeFilterData& d) {
return const_cast<Node*>(findMatchingNode(const_cast<const Node&>(root),d));
}
Loki Asari предлагает добавить третью частную функцию findMatchingNodeCommon()
, что обе версии вызова findMatchingNode()
. Тогда вы можете уйти с одним менее const_cast
. Если вы хотите перейти в крайнее, вы можете сделать шаблон findMatchingNodeCommon()
templated, а затем все const_cast
уйти. Я думаю, что это не стоит беспокоить, но они очень действительные мнения, поэтому стоит упомянуть.
Ответ 2
Как всегда, вы не можете изменять данные const
, поскольку они постоянны. В частности:
Даже несмотря на то, что const_cast может удалить константу или волатильность от любого указателя или ссылки, используя результирующий указатель или ссылку на запись в объект, объявленный const или доступ к объявленному объекту volatile вызывает поведение undefined.
(из здесь)
Итак, если указатель на корневой входной аргумент const
является разумным результатом, возвращаемый тип должен быть const
. В любом случае вы должны быть очень осторожны с возвращающими указателями: на данный момент ваша функция может вернуть указатель на временный!
Так лучше верните boost::optional<Node>
или std::optional<Node>
(если последний делает его на С++ 17, и вы используете этот стандарт).
Если ваша функция имеет смысл только для модифицируемого ввода root
(например, если результат должен быть модифицируемым, а результатом может быть адрес root
), оставьте const
в своем объявлении. Это также предотвратит временный вход root
.
Скорее всего, самое чистое решение для вашей потенциальной проблемы XY:
Если он подходит для вашего прецедента (я считаю маловероятным, чтобы этого не произошло), наиболее вероятным даже лучшим вариантом будет определение вашего алгоритма на итеративной структуре данных, например, дереве или списке (независимо от вашего Node
является node of), а затем возвращает итератор в соответствующий node или прошлый итератор (по отношению к структуре высокого уровня), если такой node не существует.
Что касается обсуждения const
-vs.-non const
: с помощью опции итератора вы можете различать константу структуры высокого уровня и ссылку node, с которой вы сравниваете вещи, t20 > итераторы над структурой высокого уровня и аргумент const &
для ссылочного ввода node (где временное сейчас не будет проблемой).
Из того, что я вижу из вашего вопроса, ваша функция может быть заменена на std::find_if
. Тогда у вас не было бы этой проблемы в первую очередь.
Ответ 3
В соответствии с Standard С++ Foundation:
Какова связь между функцией return-by-reference и функцией-константой?
Если вы хотите вернуть член своего объекта по ссылке из метода инспектора, вы должны вернуть его с помощью ссылки-на-const (const X & inspect() const) или по значению (X inspect() const).
Итак, да, вы должны возвращаться либо по значению, либо по ссылке const/указателю. Вы не используете this
, но шаблон тот же.
Ответ 4
Я бы посмотрел, как это делает STL. Там у вас есть последовательность (с разделителями итераторами), предикат, который определяет соответствие и функцию шаблона, которая выполняет поиск. Поскольку он является функцией шаблона, он автоматически сопоставляет возвращаемые значения const-qualifiers с параметрами параметров.
Если по какой-то причине вы не хотите писать функцию шаблона, вы также можете добавить две перегрузки, потому что вы можете перегружать функции с помощью разных конструкторов-констант.
Ответ 5
Я хотел бы ответить сам себе, поскольку исходный вопрос не был сформирован должным образом. Поэтому мой ответ не может считаться правильным.
Нет никакого законного способа выражения константы ввода (во время выполнения функции), не заставляя пользователя не изменять вывод (и без принудительной установки выходной константы).
Способ без _const_cast _:
static Node* findMatchingNode(Node& root, const SomeFilterData& d);
не похоже, что он гарантирует конструкцию дерева ввода.
Оригинальное объявление функции допускает такие хаки, что тоже неуместно:
const Node& root = ...
Node* result = findMatchingNode(root, filter);
result->doBadNonConstThings();