为什么不能传递 const setas const set; 到一个函数?

发布于 2024-07-27 00:18:41 字数 827 浏览 6 评论 0原文

在将其标记为重复之前,我知道 这个问题,但就我而言,我们讨论的是 const 容器。

我有 2 个类:

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

和一个函数:

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

我想调用这个函数:

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

编译器不接受这个。 为什么不? 该集合不可修改,因此不存在将非派生对象插入其中的风险。 我怎样才能以最好的方式做到这一点?

编辑:
我知道现在编译器的工作方式是 setset 完全不相关,因此找不到函数签名。 然而我现在的问题是:为什么编译器会这样工作? 是否有人反对不将 const set 视为 const set 的派生物

Before this is marked as duplicate, I'm aware of this question, but in my case we are talking about const containers.

I have 2 classes:

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

And a function:

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

I would like to invoke this function as:

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

The compiler does not accept this. Why not? The set is not modifiable so there is no risk of non-Derived objects being inserted into it. How can I do this in the best way?

Edit:
I understand that now the compiler works in a way that set<Base*> and set<Derived*> are totally unrelated and therefor the function signature is not found. My question now however is: why does the compiler work like this? Would there be any objections to not see const set<Derived*> as derivative of const set<Base*>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

生死何惧 2024-08-03 00:18:42

std::setstd::set 基本上是两个不同的对象。 尽管基类和派生类通过继承链接,但在编译器模板实例化级别,它们是两个不同的实例化(集合)。

std::set<Base*> and std::set<Derived*> are basically two different objects. Though the Base and Derived classes are linked via inheritance, at compiler template instantiation level they are two different instantiation(of set).

七婞 2024-08-03 00:18:42

首先,您没有通过引用传递似乎有点奇怪...

其次,正如另一篇文章中提到的,您最好将传入集创建为 std::set< 基数*> 然后为每个集合成员新建一个派生类。

您的问题肯定是由于这两种类型完全不同而引起的。 std::设置< 导出*> 绝不是从 std::set< 继承的 基数*> 就编译器而言。 它们只是两种不同类型的集合......

Firstly, It seems a bit odd that you aren't passing by reference ...

Secondly, as mentioned in the other post, you would be better off creating the passed-in set as a std::set< Base* > and then newing a Derived class in for each set member.

Your problem surely arises from the fact that the 2 types are completely different. std::set< Derived* > is in no way inherited from std::set< Base* > as far as the compiler is concerned. They are simply 2 different types of set ...

緦唸λ蓇 2024-08-03 00:18:42

好吧,正如您提到的问题中所述, setset 是不同的对象。 您的 register_objects() 函数采用 set 对象。 因此编译器不知道任何接受set的register_objects()。 参数的常量不会改变任何东西。 引用的问题中提出的解决方案似乎是您能做的最好的事情。 取决于你需要做什么...

Well, as stated in the question you mention, set<Base*> and set<Derived*> are different objects. Your register_objects() function takes a set<Base*> object. So the compiler do not know about any register_objects() that takes set<Derived*>. The constness of the parameter does not change anything. Solutions stated in the quoted question seem the best things you can do. Depends on what you need to do ...

迎风吟唱 2024-08-03 00:18:42

如您所知,一旦删除非常量操作,这两个类就非常相似。 然而,在 C++ 中,继承是类型的属性,而 const 只是类型之上的限定符。 这意味着您无法正确声明 const X 派生自 const Y,即使 X 派生自 Y。

此外,如果 X 不继承自 Y,则适用也适用于 X 和 Y 的所有 cv 限定变体。 这扩展到 std::set 实例化。 由于 std::set 不继承自 std::set,因此 std::setconst 不继承自 std::set; const 要么。

As you are aware, the two classes are quite similar once you remove the non-const operations. However, in C++ inheritance is a property of types, whereas const is a mere qualifier on top of types. That means that you can't properly state that const X derives from const Y, even when X derives from Y.

Furthermore, if X does not inherit from Y, that applies to all cv-qualified variants of X and Y as well. This extends to std::set instantiations. Since std::set<Foo> does not inherit from std::set<bar>, std::set<Foo> const does not inherit from std::set<bar> const either.

薆情海 2024-08-03 00:18:42

您说得很对,这在逻辑上是允许的,但它需要进一步的语言功能。 如果您有兴趣了解另一种语言的实现方式,它们可以在 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

You are quite right that this is logically allowable, but it would require further language features. They are available in C# 4.0, if you're interested in seeing another language's way of doing it. See here: 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

静若繁花 2024-08-03 00:18:42

还没有看到它链接,所以这里是 C++ FAQ Lite 中与此相关的要点:

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

我认为他们的苹果袋!=袋水果的类比适合这个问题。

Didn't see it linked yet, so here's a bullet point in the C++ FAQ Lite related to this:

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

I think their Bag-of-Apples != Bag-of-Fruit analogy suits the question.

爱的故事 2024-08-03 00:18:41

编译器不接受这一点的原因是标准告诉它不要这样做。

标准告诉它不要这样做的原因是,委员会没有引入一条规则,即 const MyTemplate是与 const MyTemplate相关的类型。 即使非常量类型不相关。 他们当然不希望 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 作为 const MyTemplate 传递到底意味着什么? 这两个类没有共同的成员函数,并且布局不兼容。 您需要在两者之间使用转换运算符,否则编译器将不知道要做什么,无论它们是否为 const。 但根据标准中定义模板的方式,即使没有模板专门化,编译器也不知道要做什么。

std::set 本身可以提供一个转换运算符,但这只需要制作一个副本(*),您可以自己轻松完成。 如果存在 std::immutable_set 这样的东西,那么我认为可以实现这样的 std::immutable_set只需通过指向相同的 pImpl 即可从 std::immutable_set 构建。 即便如此,如果在派生类中重载了非虚拟运算符,就会发生奇怪的事情 - 基容器将调用基版本,因此如果它有一个非默认比较器执行任何操作,则转换可能会取消集合的排序对象本身而不是它们的地址。 因此,这种转变将伴随着严重的警告。 但无论如何,不​​存在 immutable_set,并且 const 与 immutable 不是一回事。

另外,假设 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());

[编辑:您已更改代码示例以不按值传递。 我的代码仍然有效,而且除了重构调用代码以首先使用 std::set 之外,据我所知,这是您能做的最好的事情。]

std::set 确保所有元素都是 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 实现中有点不可信,即使不是这样,我怀疑委员会是否会想要强制执行如此重量级的实现细节。

The reason the compiler doesn't accept this is that the standard tells it not to.

The reason the standard tells it not to, is that the committee did not what to introduce a rule that const MyTemplate<Derived*> is a related type to const MyTemplate<Base*> even though the non-const types are not related. And they certainly didn't want a special rule for std::set, since in general the language does not make special cases for library classes.

The reason the standards committee didn't want to make those types related, is that MyTemplate might not have the semantics of a container. Consider:

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();
};

Then what does it even mean to pass a const MyTemplate<Derived*> as a const MyTemplate<Base*>? The two classes have no member functions in common, and aren't layout-compatible. You'd need a conversion operator between the two, or the compiler would have no idea what to do whether they're const or not. But the way templates are defined in the standard, the compiler has no idea what to do even without the template specializations.

std::set itself could provide a conversion operator, but that would just have to make a copy(*), which you can do yourself easily enough. If there were such a thing as a std::immutable_set, then I think it would be possible to implement that such that a std::immutable_set<Base*> could be constructed from a std::immutable_set<Derived*> just by pointing to the same pImpl. Even so, strange things would happen if you had non-virtual operators overloaded in the derived class - the base container would call the base version, so the conversion might de-order the set if it had a non-default comparator that did anything with the objects themselves instead of their addresses. So the conversion would come with heavy caveats. But anyway, there isn't an immutable_set, and const is not the same thing as immutable.

Also, suppose that Derived is related to Base by virtual or multiple inheritance. Then you can't just reinterpret the address of a Derived as the address of a Base: in most implementations the implicit conversion changes the address. It follows that you can't just batch-convert a structure containing Derived* as a structure containing Base* without copying the structure. But the C++ standard actually allows this to happen for any non-POD class, not just with multiple inheritance. And Derived is non-POD, since it has a base class. So in order to support this change to std::set, the fundamentals of inheritance and struct layout would have to be altered. It's a basic limitation of the C++ language that standard containers cannot be re-interpreted in the way you want, and I'm not aware of any tricks that could make them so without reducing efficiency or portability or both. It's frustrating, but this stuff is difficult.

Since your code is passing a set by value anyway, you could just make that copy:

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

[Edit: you've changed your code sample not to pass by value. My code still works, and afaik is the best you can do other than refactoring the calling code to use a std::set<Base*> in the first place.]

Writing a wrapper for std::set<Base*> that ensures all elements are Derived*, the way Java generics work, is easier than arranging for the conversion you want to be efficient. So you could do something like:

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);

(*) Actually, I guess it could copy-on-write, which in the case of a const parameter used in the typical way would mean it never needs to do the copy. But copy-on-write is a bit discredited within STL implementations, and even if it wasn't I doubt the committee would want to mandate such a heavyweight implementation detail.

假面具 2024-08-03 00:18:41

如果您的 register_objects 函数接收一个参数,它可以在其中放置/期望任何 Base 子类。 签名就是这么说的。

这违反了里氏替换原则。

这个特殊问题也称为协方差。 在这种情况下,如果你的函数参数是一个常量容器,那么它就可以工作。 如果参数容器是可变的,则它无法工作。

If your register_objects function receives an argument, it can put/expect any Base subclass in there. That's what it's signature sais.

It's a violation of the Liskov substitution principle.

This particular problem is also referred to as Covariance. In this case, where your function argument is a constant container, it could be made to work. In case the argument container is mutable, it can't work.

万人眼中万个我 2024-08-03 00:18:41

首先看这里:派生数组是否与基数数组。 在您的情况下,派生集是与基集集完全不同的容器,并且由于没有隐式转换运算符可用于在它们之间进行转换,因此编译器会给出错误。

Take a look here first: Is array of derived same as array of base. In your case set of derived is a totally different container from set of base and since there is no implicit conversion operator is available to convert between them , compiler is giving an error.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文