英文:
Defining a conversion function for a template specialisation
问题
The error you encountered in your code and the error message you mentioned are indeed related to the lack of an appropriate conversion between vector<D>
and vector<B>
. You are correct in your understanding that the issue is not that a vector<Shape>
is required, but rather that the assignment between vector<D>
and vector<B>
is not implicitly allowed because of the lack of a suitable conversion or assignment operator.
Regarding your attempt to define a conversion function for the vector<D>
specialization, it appears you're trying to specialize the operator=
for this specific case. The error you received, "specializing member 'Vector
To provide a specialization for the assignment operator between vector<D>
and vector<B>
, you would need to do something like this:
template<>
vector<D>& vector<D>::operator=(const vector<B>& other) {
// Your code to perform the conversion and assignment here
return *this;
}
In this specialized assignment operator, you would need to manually implement the logic to convert elements of vector<B>
into vector<D>
. Keep in mind that implementing such a conversion can be complex and may involve creating new D
objects from B
objects or handling other specific cases, depending on your requirements.
Remember that C++ is designed to be explicit about type conversions and assignments to ensure type safety, which is why implicit conversions between unrelated types like vector<D>
and vector<B>
are not allowed by default. Specializing the assignment operator is one way to provide such conversions, but it should be done carefully to ensure the correctness of the conversion logic.
英文:
The following excerpt is from pages 686-687 of Programming: Principles and Practice by Bjarne Stroustrup:
> 19.3.4 Containers and inheritance
>
> There is one kind of combination of object-oriented programming and generic programming that people always try, but it doesn’t work: attempting to use a container of objects of a derived class as a container of objects of a base class. For example:
>
>
> vector<Shape> vs;
> vector<Circle> vc;
>
> vs = vc; // error: vector<Shape> required
>
> void f(vector<Shape>&);
> f(vc); // error: vector<Shape> required
>
>
> But why not? After all, you say, I can convert a Circle to a Shape! Actually, no, you can’t. You can convert a Circle* to a Shape* and a Circle& to a Shape&, but we deliberately disabled assignment of Shapes, so that you wouldn’t have to wonder what would happen if you put a Circle with a radius into a Shape variable that doesn’t have a radius (§14.2.4). What would have happened — had we allowed it — would have been what is called “slicing” and is the class object equivalent of integer truncation (§3.9.2).
I'm not sure I understand why vs = vc
is an error. The annotation says // error: vector<Shape> required
. I tried the following toy example:
#include "../std_lib_facilities.h"
class B {
};
class D : public B {
int x;
public:
D() {
x = 7;
}
};
class D2 : B {
};
// vector<D>& vector<D>::vector<B>() {
// return vector<B>;
// }
int main() {
vector<B> vb;
vector<D> vd;
vb = vd;
return 0;
}
When I attempt to compile it, the error message I get reads:
> test.cpp:35:11: error: no match for 'operator=' (operand types are 'Vector<B>' and Vector<D>
)
> 35 | vd2 = vd;
> | ^~
> In file included from test.cpp:1:
> ../std_lib_facilities.h:70:27: note: candidate: 'Vector<B>& Vector<B>::operator=(const Vector<B>&)'
> 70 | template< class T> struct Vector : public std::vector<T>
> | ^~~~~~
> ../std_lib_facilities.h:70:27: note: no known conversion for argument 1 from 'Vector<D>' to 'const Vector<B>&'
> ../std_lib_facilities.h:70:27: note: candidate: 'Vector<B>& Vector<B>::operator=(Vector<B>&&)'
> ../std_lib_facilities.h:70:27: note: no known conversion for argument 1 from 'Vector<D>' to 'Vector<B>&&'
So, it seems that it's not so much that a vector<Shape>
is required, but rather that vector<Shape>
does not have an assignment operator that takes a vector<Circle>
as input, and vector<Circle>
does not have the conversion function vector<Shape>()
. Is this correct?
This also got me thinking about whether it would be possible to supply a conversion function for the vector<D>
specialisation myself, but when I try to define vector<D>& vector<D>::vector<B>()
, I get the following error:
> test.cpp:22:10: error: specializing member 'Vector<D>::operator=' requires 'template<>' syntax
> 22 | vector<D>& vector<D>::vector<B>() {
> | ^
I'm not sure how to write it in template syntax, or if it is even possible to do so. I don't quite see the problem with being able to augment a specialisation with our own member functions. In the last paragraph of the section, Stroustrup says:
> Inheritance is a powerful and subtle mechanism and templates do not implicitly extend its reach. [..] Just remember that "D
is a B
" does not imply "C<D>
is a C<B>
" for an arbitrary template C
.
So, "D
is a B
" does not imply "C<D>
is a C<B>
", but that doesn't mean that both can't be true at the same time. So, why wasn't I able to define the conversion function?
答案1
得分: 2
在我看来,不幸的是,这本书混淆了两个问题。它们密切相关,但每个问题都值得深入解释。
一个问题是std::vector<A>
不能转换为std::vector<B>
,即使A
可以转换为B
。std::vector
简单地没有提供这种转换。
另一个问题是,当你使用继承时,大多数情况下使用std::vector<base>
都不是一个好主意。关于这点,我建议你参考https://stackoverflow.com/questions/274626/what-is-object-slicing。
另外要注意,该书特别讨论了std::vector
。没有什么可以阻止你在A
可以转换为B
时提供从foo<A>
到foo<B>
的转换(请参考以下链接)。标准库有时也会这样做。例如,当Y*
可以转换为T*
时,可以从Y*
构造出一个shared_ptr<T>
(请参阅https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr)。此外,std::pair
启用了隐式转换,例如以下代码可以编译通过:
std::pair<int, int> x;
std::pair<double, double> y;
x = y;
关于为什么这样可以工作,请参阅https://en.cppreference.com/w/cpp/utility/pair/pair。
std::vector
没有此类转换,您不能轻松添加它们。您不能向std::vector
添加成员,也不能为std::vector<my_type>
添加特化。请考虑到隐式转换通常会带来更多问题而不是好处,因为它们可能隐藏错误:
void foo(const std::vector<A>&);
void bar(const std::vector<B>&);
std::vector<A> a;
bar(a); // 错误,但如果有转换可用,将会编译通过
通常会将转换设置为显式,并仅在其使用自然且所期望时启用隐式转换(例如,shared_ptr
就是这样,因为它与原始指针的行为相同)。
另一方面,没有什么能阻止您编写一个函数来使转换显式化:
std::vector<B> convert(const std::vector<A>&);
bar(convert(a)); // 显式是好的!
不要为std::vector
定义转换。您可以为自己的模板定义转换,如下所示:
struct A {};
struct B {
operator A() const { return {}; }
};
template <typename T>
struct foo {
T t;
template <typename U>
operator foo<U>() const {
return {static_cast<U>(t)};
}
};
int main() {
foo<A> fa;
foo<B> fb;
fa = fb; // 使用隐式转换
//fb = fa; // 错误
}
但这只是一个刻意构造的示例。实际上,你几乎总是希望将转换操作符定义为explicit
。
英文:
In my humble opinion it is unfortunate that the book conflates two issues. They are very much related, but each on its own would deserve an in depth explanation.
One is that std::vector<A>
can't be converted to std::vector<B>
even though A
can be converted to B
. std::vector
simply does not provide the conversion.
The other issue is that when you are using inheritance then most of the time a std::vector<base>
is a bad idea. For this I refer you to https://stackoverflow.com/questions/274626/what-is-object-slicing.
Further note that the book is talking about std::vector
specifically. There is nothing that prevents you to provide a conversion from foo<A>
to foo<B>
when A
can be converted to B
(see below). And the standard library does that sometimes. For example a shared_ptr<T>
can be constructed from a Y*
when Y*
can be converted to a T*
(see here https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr). Also std::pair
has implicit conversions enabled, for example the following compiles:
std::pair<int,int> x;
std::pair<double,double> y;
x = y;
For why this works see here: https://en.cppreference.com/w/cpp/utility/pair/pair.
std::vector
does not have such conversions and you cannot easily add them. You cannot add members to std::vector
neither are you allowed to add specializations for std::vector<my_type>
. Consider that implicit conversions often do more harm than good, because they potentially hide bugs:
void foo(const std::vector<A>&);
void bar(const std::vector<B>&);
std::vector<A> a;
bar(a); // ups, typo, but would compile if conversions would be available
It is common to make conversions explicit and only enable implicit conversions when their use is natural and desired (as for example with shared_ptr
its natural, because its just what raw pointers do as well).
On the other hand, nothing forbids you to write a function to make the conversion explicit:
std::vector<B> convert(const std::vector<A>&);
bar( convert(a) ); // explicit is good!
> So, "D is a B" does not imply "C<D> is a C<B>", but that doesn't mean that both can't be true at the same time. So, why wasn't I able to define the conversion function?
You should not define the conversion for std::vector
. You can do it for your own template:
struct A {};
struct B {
operator A() const { return {}; }
};
template <typename T>
struct foo {
T t;
template <typename U>
operator foo<U>() const {
return {static_cast<U>(t)};
}
};
int main() {
foo<A> fa;
foo<B> fb;
fa = fb; // uses implicit conversion
//fb = fa; // error
}
However, this is just a contrived example. In reality you almost always want the conversion operators to be explicit
.
答案2
得分: 2
是的,两种不同类型的vector<Shape>
和vector<Circle>
不能隐式地相互转换。
所以,为什么我不能定义转换函数呢?
你没有向我们展示你尝试了什么,只是显示了错误。
要专门化std::vector<D>
,你需要类似于以下内容:
template<>
class std::vector<D> {
public:
std::vector<D>& operator=(const std::vector<B>&) {
return *this;
}
// 所有其他复杂的 std::vector 实现细节
};
但是专门化std::vector
可能不被允许,并且需要大量的工作,所以这行不通。
你可以继承自std::vector<D>
并添加你的转换函数:
class vectorD : public std::vector<D> {
public:
using std::vector<D>::vector;
using std::vector<D>::operator=;
vectorD& operator=(const std::vector<B>&) {
return *this;
}
};
请注意,从std::
类继承通常是不受欢迎的,但没有什么阻止你这样做,只要你不将基类指针传递给vector<D>
并期望能够通过其中一个来delete
一个vectorD
,通常都可以接受。
第三种选择是在D
本身中定义如何从B
转换为D
。这在大多数情况下是有道理的:
class D : public B {
int x = 7;
public:
D() = default;
D(const B&) {} // 定义如何将 B 转换为 D
};
然后你可以简单地使用vector
的assign
成员函数:
vd.assign(vb.begin(), vb.end());
英文:
> Is this correct?
Yes, the two distinct types vector<Shape>
and vector<Circle>
can't be implicitly converted into eachother.
> So, why wasn't I able to define the conversion function?
You didn't show us what you tried, only the error.
In order to specialize std::vector<D>
you'd need something like this:
template<>
class std::vector<D> {
public:
std::vector<D>& operator=(const std::vector<B>&) {
return *this;
}
// all the rest of the gory std::vector implementation details
};
But specializing std::vector
may not be allowed and would require a lot of work so that's not going to fly.
You could inherit from std::vector<D>
and add your conversion function:
class vectorD : public std::vector<D> {
public:
using std::vector<D>::vector;
using std::vector<D>::operator=;
vectorD& operator=(const std::vector<B>&) {
return *this;
}
};
Note that inherting from std::
classes is often frowned upon but there's nothing preventing you to do it and as long as you don't pass base class pointers to vector<D>
around and expect to be able to delete
a vectorD
thorugh one of those, it's ususally fine.
A third option would be to define how to convert from B
to D
in D
itself. This makes sense in most situations:
class D : public B {
int x = 7;
public:
D() = default;
D(const B&) {} // define how to convert a B into a D
};
You can then simply use the assign
member function of the vector
:
vd.assign(vb.begin(), vb.end());
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论