Причина, по которой компилятор не принимает это, заключается в том, что стандарт запрещает это делать.
Причина, по которой стандарт не говорит об этом, состоит в том, что комитет не сделал, чтобы ввести правило, что 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, и даже если бы это было не так, я сомневаюсь, что комитет захочет навязать такие подробные детали реализации.