Почему невозможно передать набор констант <Derived *> как набор констант <Base *> в функцию? - PullRequest
2 голосов
/ 13 июля 2009

До того, как это будет помечено как дубликат, я знаю этот вопрос, но в моем случае речь идет о контейнерах const.

У меня есть 2 класса:

class Base { };
class Derived : public Base { };

И функция:

void register_objects(const std::set<Base*> &objects) {}

Я хотел бы вызвать эту функцию как:

std::set<Derived*> objs;
register_objects(objs);

Компилятор не принимает это. Почему бы и нет? Набор не подлежит изменению, поэтому нет риска вставки в него не производных объектов. Как я могу сделать это наилучшим образом?

Edit:
Я понимаю, что теперь компилятор работает таким образом, что set<Base*> и set<Derived*> абсолютно не связаны и поэтому сигнатура функции не найдена. Однако теперь мой вопрос: почему компилятор работает так? Будут ли какие-либо возражения, чтобы не рассматривать const set<Derived*> как производную от const set<Base*>

Ответы [ 9 ]

13 голосов
/ 13 июля 2009

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

Причина, по которой стандарт не говорит об этом, состоит в том, что комитет не сделал, чтобы ввести правило, что const MyTemplate<Derived*> является связанным типом с const MyTemplate<Base*>, даже если неконстантные типы не связаны. И они, конечно, не хотели специального правила для std :: set, так как в общем случае язык не делает особых случаев для библиотечных классов.

Причина, по которой комитет по стандартам не хотел связывать эти типы, заключается в том, что MyTemplate может не иметь семантики контейнера. Рассмотрим:

template <typename T>
struct MyTemplate {
    T *ptr;
};

template<>
struct MyTemplate<Derived*> {
    int a;
    void foo();
};

template<>
struct MyTemplate<Base*> {
    std::set<double> b;
    void bar();
};

Тогда что вообще значит передавать const MyTemplate<Derived*> как const MyTemplate<Base*>? Два класса не имеют общих функций-членов и не совместимы с макетом. Вам понадобится оператор преобразования между ними, иначе компилятор не будет знать, что делать, являются ли они константными или нет. Но так как шаблоны определены в стандарте, компилятор не знает, что делать даже без специализаций шаблонов.

std::set сам мог бы предоставить оператор преобразования, но для этого нужно было бы просто сделать копию (*), которую вы можете сделать самостоятельно достаточно легко. Если бы существовала такая вещь, как std::immutable_set, то я думаю, что было бы возможно реализовать ее так, чтобы std::immutable_set<Base*> мог быть построен из std::immutable_set<Derived*>, просто указав на тот же pImpl. Даже в этом случае могут произойти странные вещи, если в производном классе будут перегружены не виртуальные операторы - базовый контейнер будет вызывать базовую версию, поэтому преобразование может переупорядочить набор, если у него есть компаратор не по умолчанию, который что-либо делает сами объекты вместо их адресов. Таким образом, преобразование будет сопровождаться тяжелыми оговорками. Но в любом случае, immutable_set не существует, и const - это не то же самое, что неизменный.

Также предположим, что Derived связан с Base виртуальным или множественным наследованием. Тогда вы не можете просто интерпретировать адрес Derived как адрес Base: в большинстве реализаций неявное преобразование изменяет адрес. Из этого следует, что вы не можете просто пакетно преобразовать структуру, содержащую Derived*, как структуру, содержащую Base*, без копирования структуры. Но стандарт C ++ фактически допускает это для любого не POD-класса, а не только для множественного наследования. И Derived не является POD, так как имеет базовый класс. Таким образом, чтобы поддержать это изменение на std::set, основы наследования и структуры структуры должны быть изменены. Базовое ограничение языка C ++ заключается в том, что стандартные контейнеры нельзя интерпретировать так, как вы хотите, и мне неизвестны какие-либо хитрости, которые могли бы их сделать без снижения эффективности, переносимости или того и другого. Это расстраивает, но это сложно.

Так как ваш код в любом случае передает набор по значению, вы можете просто сделать эту копию:

std::set<Derived*> objs;
register_objects(std::set<Base*>(objs.begin(), objs.end());

[Редактировать: вы изменили пример кода, чтобы не передавать по значению. Мой код по-прежнему работает, и afaik - это лучшее, что вы можете сделать, кроме рефакторинга вызывающего кода для использования std::set<Base*> в первую очередь.]

Написание оболочки для std::set<Base*>, которая гарантирует, что все элементы Derived*, способ работы дженериков Java, проще, чем организация преобразования, которое вы хотите сделать эффективным. Таким образом, вы можете сделать что-то вроде:

template<typename T, typename U>
struct MySetWrapper {
    // Requirement: std::less is consistent. The default probably is, 
    // but for all we know there are specializations which aren't. 
    // User beware.
    std::set<T> content;
    void insert(U value) { content.insert(value); }
    // might need a lot more methods, and for the above to return the right
    // type, depending how else objs is used.
};

MySetWrapper<Base*,Derived*> objs;
// insert lots of values
register_objects(objs.content);

(*) На самом деле, я думаю, что это может копировать при записи, что в случае параметра const, используемого обычным способом, означало бы, что копирование не требуется. Но копирование при записи немного дискредитируется в реализациях STL, и даже если бы это было не так, я сомневаюсь, что комитет захочет навязать такие подробные детали реализации.

2 голосов
/ 13 июля 2009

Сначала посмотрите здесь: Является ли массив производных таким же, как массив base . В вашем случае набор производных является совершенно другим контейнером, чем набор базовых, и, поскольку нет никакого неявного оператора преобразования, доступного для преобразования между ними, компилятор выдает ошибку.

2 голосов
/ 13 июля 2009

Если ваша функция register_objects получает аргумент, она может поместить / ожидать любой подкласс Base. Вот что подпись говорит.

Это нарушение принципа подстановки Лискова.

Эта конкретная проблема также упоминается как Ковариация . В этом случае, когда аргумент вашей функции является константным контейнером, он может работать . Если контейнер аргументов изменчив, он не может работать.

2 голосов
/ 13 июля 2009

std::set<Base*> и std::set<Derived*> - это два разных объекта. Хотя классы Base и Derived связаны через наследование, на уровне экземпляров шаблона компилятора они представляют собой два разных экземпляра (из набора).

1 голос
/ 13 июля 2009

Во-первых, кажется немного странным, что вы не передаете по ссылке ...

Во-вторых, как упоминалось в другом посте, было бы лучше создать переданный набор как std :: set , а затем добавить новый класс Derived для каждого члена набора.

Ваша проблема наверняка возникает из-за того, что эти 2 типа совершенно разные. std :: set никоим образом не наследуется от std :: set в том, что касается компилятора. Это просто 2 разных типа набора ...

0 голосов
/ 13 июля 2009

Еще не видел его связанным, поэтому вот пункт в C ++ FAQ Lite, связанный с этим:

http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.3

Я думаю, что их сумка с яблоками! = Аналогия с сумкой с фруктами подходит для вопроса.

0 голосов
/ 13 июля 2009

Вы совершенно правы, что это логически допустимо, но для этого потребуются дополнительные языковые функции. Они доступны в C # 4.0, если вы заинтересованы в том, чтобы сделать это на другом языке. Смотрите здесь: http://community.bartdesmet.net/blogs/bart/archive/2009/04/13/c-4-0-feature-focus-part-4-generic-co-and-contra-variance-for-delegate-and-interface-types.aspx

0 голосов
/ 13 июля 2009

Как вы знаете, эти два класса довольно похожи после удаления неконстантных операций. Однако в C ++ наследование является свойством типов, тогда как const является простым классификатором поверх типов. Это означает, что вы не можете правильно утверждать, что const X происходит от const Y, даже если X происходит от Y.

Кроме того, если X не наследуется от Y, это также относится ко всем cv-квалифицированным вариантам X и Y. Это распространяется на std::set экземпляров. Поскольку std::set<Foo> не наследуется от std::set<bar>, std::set<Foo> const также не наследуется от std::set<bar> const.

0 голосов
/ 13 июля 2009

Ну, как указано в упомянутом вами вопросе, set<Base*> и set<Derived*> - это разные объекты Ваша функция register_objects () принимает объект set<Base*>. Таким образом, компилятор не знает ни о каком register_objects (), который принимает set<Derived*>. Постоянство параметра ничего не меняет. Решения, изложенные в приведенном вопросе, кажутся лучшими, что вы можете сделать. Зависит от того, что вам нужно сделать ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...