定义模板特化的转换函数

huangapple go评论69阅读模式
英文:

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::operator=' requires 'template<>' syntax," indicates that you need to provide a template specialization for this operation. You're on the right track by considering this, but the correct syntax for specializing assignment operators is a bit more involved.

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:
>
>
&gt; vector&lt;Shape&gt; vs;
&gt; vector&lt;Circle&gt; vc;
&gt;
&gt; vs = vc; // error: vector&lt;Shape&gt; required
&gt;
&gt; void f(vector&lt;Shape&gt;&amp;);
&gt; f(vc); // error: vector&lt;Shape&gt; required
&gt;

>
> 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&lt;Shape&gt; required. I tried the following toy example:

#include &quot;../std_lib_facilities.h&quot;

class B {
    
};

class D : public B {
    int x;

public:
    D() {
        x = 7;
    }
};

class D2 : B {
    
};

// vector&lt;D&gt;&amp; vector&lt;D&gt;::vector&lt;B&gt;() {
//     return vector&lt;B&gt;;
// }

int main() {

    vector&lt;B&gt; vb;
    vector&lt;D&gt; 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&lt;D&gt;)
> 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&lt;Shape&gt; is required, but rather that vector&lt;Shape&gt; does not have an assignment operator that takes a vector&lt;Circle&gt; as input, and vector&lt;Circle&gt; does not have the conversion function vector&lt;Shape&gt;(). Is this correct?

This also got me thinking about whether it would be possible to supply a conversion function for the vector&lt;D&gt; specialisation myself, but when I try to define vector&lt;D&gt;&amp; vector&lt;D&gt;::vector&lt;B&gt;(), 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&lt;D&gt; is a C&lt;B&gt;" for an arbitrary template C.

So, "D is a B" does not imply "C&lt;D&gt; is a C&lt;B&gt;", 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可以转换为Bstd::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; // 错误
}

Live Demo

但这只是一个刻意构造的示例。实际上,你几乎总是希望将转换操作符定义为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&lt;A&gt; can't be converted to std::vector&lt;B&gt; 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&lt;base&gt; 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&lt;A&gt; to foo&lt;B&gt; when A can be converted to B (see below). And the standard library does that sometimes. For example a shared_ptr&lt;T&gt; 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&lt;int,int&gt; x;
 std::pair&lt;double,double&gt; 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&lt;my_type&gt;. Consider that implicit conversions often do more harm than good, because they potentially hide bugs:

void foo(const std::vector&lt;A&gt;&amp;);
void bar(const std::vector&lt;B&gt;&amp;);

std::vector&lt;A&gt; 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&lt;B&gt; convert(const std::vector&lt;A&gt;&amp;);

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 &lt;typename T&gt;
struct foo {
    T t;

    template &lt;typename U&gt;
    operator foo&lt;U&gt;() const {
        return {static_cast&lt;U&gt;(t)};
    }
};

int main() {
    foo&lt;A&gt; fa;
    foo&lt;B&gt; fb;
    fa = fb;   // uses implicit conversion 
    //fb = fa; // error 
}

Live Demo

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

然后你可以简单地使用vectorassign成员函数:

vd.assign(vb.begin(), vb.end());
英文:

> Is this correct?

Yes, the two distinct types vector&lt;Shape&gt; and vector&lt;Circle&gt; 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&lt;D&gt; you'd need something like this:

template&lt;&gt;
class std::vector&lt;D&gt; {
public:
    std::vector&lt;D&gt;&amp; operator=(const std::vector&lt;B&gt;&amp;) {
        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&lt;D&gt; and add your conversion function:

class vectorD : public std::vector&lt;D&gt; {
public:
    using std::vector&lt;D&gt;::vector;
    using std::vector&lt;D&gt;::operator=;

    vectorD&amp; operator=(const std::vector&lt;B&gt;&amp;) {
        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&lt;D&gt; 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&amp;) {}      // 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());

huangapple
  • 本文由 发表于 2023年5月24日 21:23:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76324041.html
  • c++
  • inheritance
  • templates