【问题标题】:candidate function not viable: no known conversion from std::vector<derived> to std::vector<base>候选函数不可行:没有已知的从 std::vector<derived> 到 std::vector<base> 的转换
【发布时间】:2018-05-31 14:48:38
【问题描述】:

首先,标题可能无法反映当前问题,因此请随时更改。假设我有以下课程;

#include <iostream>
#include <vector>

template <typename K, class V>
class A {
public:
  K x;
  V y;
  A(K x, V y):x(x), y(y) {}
  void print(A<K, V>& z) {
    std::cout << x + z.x << "-" << y + z.y << std::endl;
  }
  void print(std::vector<A<K,V>> z) {
    for(auto& i:z) {
      print(i);
    }
  }
};

class B:public A<int, std::string> {
public:
  B():A(0, "zero") {}
  B(int x, std::string y):A(x, y) {}
};

void test() {
  B b1(1, "one");
  B b2(2, "two");
  B b3(3, "three");
  B b4(4, "four");
  B b5(5, "five");
  b5.print(b1);
  //
  std::vector<B> c;
  c.push_back(b1);
  c.push_back(b2);
  c.push_back(b3);
  c.push_back(b4);
  b5.print(c);
}

我在最后一行 (b5.print(c)) 收到以下错误;

test_class.cpp:40:6: error: no matching member function for call to 'print'
  b5.print(c);
  ~~~^~~~~
test_class.cpp:10:8: note: candidate function not viable: no known conversion from 'std::vector<B>' to 'A<int, std::__1::basic_string<char> > &' for 1st argument
  void print(A<K, V>& z) {
       ^
test_class.cpp:13:8: note: candidate function not viable: no known conversion from 'vector<B>' to 'vector<A<int, std::__1::basic_string<char> >>' for 1st argument
  void print(std::vector<A<K,V>> z) {
       ^
1 error generated.

我基本上期望从vector&lt;B&gt;std::vector&lt;A&lt;int,std::string&gt;&gt; 的隐式转换,但事实并非如此。因此,我想出了两个解决方案。

  1. 定义typedef std::vector&lt;A&lt;int,std::string&gt;&gt; MyWeirdVector; 在 A 类中并使用 se B::MyWeirdVector c; 而不是 std::vector&lt;B&gt; c;
  2. 在 A 类中将每个打印函数定义为 template &lt;typename U&gt;,并接受类型名 U 作为参数。

这两种解决方案都有其自身的缺点。首先,我必须将 c 实例化为 B::MyWeirdVector,其次,我(感觉)没有类型安全性。即使我没有在 &lt;&gt; 中定义类型,第二种解决方案也有效。

那么,对于这个问题,是否有一个优雅的解决方案,比如让隐式类型从 std::vector&lt;B&gt; 转换为 std::vector&lt;A&lt;int,std::string&gt;&gt;

-- 编辑--

感谢@max66 和@Caleth 以及其他伙伴。我只想分享完整的工作示例。请注意,如果您不想发疯,@max66 的答案在print 之前没有void。 (1.所有打印函数参数都是const,2.合并来自@max66和@Caleth的答案。)

#include <iostream>
#include <vector>
#include <type_traits>

template <typename K, class V>
class A {
public:
  K x;
  V y;
  A(K x, V y):x(x), y(y) {}
  void print(const A<K, V>& z) {
    std::cout << x + z.x << "-" << y + z.y << std::endl;
  }

  // for C++11, thanks to @Caleth
  // template <typename Container, typename = typename std::enable_if<!std::is_base_of< A<K,V>, typename std::remove_reference<Container>::type >::value>::type>
  // void print(Container&& z) {
  //   for(auto& i:z) {
  //     print(i);
  //   }
  // }

  // thanks to @max66
  template <typename T>
  typename std::enable_if<std::is_base_of<A<K, V>, T>::value>::type
    print(std::vector<T> const & z) {
      for(auto const & i:z) print(i);
    }    
  };

class B:public A<int, std::string> {
public:
  B():A(0, "zero") {}
  B(int x, std::string y):A(x, y) {}
};

void test() {
  B b1(1, "one");
  B b2(2, "two");
  B b3(3, "three");
  B b4(4, "four");
  B b5(5, "five");
  b5.print(b1);
  //
  std::vector<B> c;
  c.push_back(b1);
  c.push_back(b2);
  c.push_back(b3);
  c.push_back(b4);
  b5.print(c);
}

【问题讨论】:

  • 这是个通病,std::vector&lt;A&gt;std::vector&lt;B&gt;是完全不相关的类型,即使AB是相关的。
  • 打印函数不应该是A的成员(可以是静态方法或独立的可能是友元函数),它应该接受常量引用作为参数。
  • @Slava print 正在使用它被调用的实例的成员,以及它的参数的成员
  • @Sezen 我错过了它使用对象数据,是的,在这种情况下它很好,但它应该是接受 const 引用的 const 方法。
  • 注意:如果您的打印向量会以某种方式工作,那么当您按值传递向量时,就会出现切片问题。

标签: c++ c++11 inheritance vector type-conversion


【解决方案1】:

怎么样

template <typename T>
void print(std::vector<T> const & z) {
  for(auto const & i:z) {
    print(i);
  }
}

而不是

void print(std::vector<A<K,V>> z) {
  for(auto& i:z) {
    print(i);
  }
}

?

我的意思是:您不能进行从 std::vector&lt;B&gt;std::vector&lt;A&lt;K, T&gt;&gt; 的隐式转换,但您可以管理通用 std::vector&lt;T&gt;(通用 T)的内容并从 T 获取(以防万一)隐式转换A&lt;K, T&gt; 的元素(如果 T 是派生类型)。

如果需要,您可以添加std::enable_if 以启用模板打印功能,前提是T 派生自A&lt;K, T&gt;

-- 编辑--

OP 询问

如何使用std::enable_if使模板打印功能只对A派生的对象进行操作?

有很多方法;例如,请参阅 Caleth 的答案,其中包含额外的模板类型和 std::enable_if 以激活它。

但我更喜欢std::enable_if激活的返回值。

类似(注意:代码未测试)

template <typename T>
typename std::enable_if<std::is_base_of<A<K, V>, T>::value>::type
   print(std::vector<T> const & z)
 { for(auto const & i:z) print(i); }

如果你可以使用 C++14,你可以稍微简化一下(使用 std::enable_if_t&lt;&gt; 而不是 typename std::enable_if&lt;&gt;::type

template <typename T>
std::enable_if_t<std::is_base_of<A<K, V>, T>::value>
   print(std::vector<T> const & z)
 { for(auto const & i:z) print(i); }

并更多地使用 C++17(std::is_base_of_v&lt;&gt; 而不是 `std::is_base_of::value)

template <typename T>
std::enable_if_t<std::is_base_of_v<A<K, V>, T>>
   print(std::vector<T> const & z)
 { for(auto const & i:z) print(i); }

【讨论】:

  • 如何使用std::enable_if使模板打印功能只对A派生的对象进行操作?这承诺。如果您能用这个扩展答案,我将不胜感激。
  • @Sezen - 答案改进;希望这会有所帮助(但请注意:未经测试的代码)。
  • 谢谢@max66。在您和@Caleth 的帮助下,我能够将代码转换为 C++11 并进行测试。这里是:` template , T >::value>::type> void print(const std::vector& z) { for(auto& i:z) { print(i); } }` 你能编辑答案吗?
  • @Sezen - 我不鼓励第二种类型的解决方案,因为可以“劫持”解释模板参数;我的意思是:如果你调用b5.print&lt;std::string, void&gt;(std::vector&lt;std::string&gt;{"abc", "123"}),你就会激活该方法(显然调用另一个print()会出错);在返回值上使用std::enable_if,该方法不能被“劫持”。
  • 我无法让您的示例正常工作,因此,我以某种方式混合了您和@Caleth 的答案:)。你能给出一个没有第二类名称解决方案的测试和工作示例吗?
【解决方案2】:

将每个打印函数定义为模板&lt;typename U&gt;

除此之外,请仅使用类型名定义引发错误的打印函数。

由于这两种类型完全不同,因此不能选择隐式转换,但我的建议是。

【讨论】:

    【解决方案3】:

    为了最大的通用性:

    template<typename Container, typename = std::enable_if_t<!std::is_base_of_v<A<K, V>, std::remove_reference_t<Container>>>>
    void print(Container&& z) {
      for(auto & i : z) {
        print(i);
      }
    }
    

    这个类型安全的。如果您尝试传递不是(可能嵌套的)A&lt;K, V&gt; 容器的内容,则模板实例化将失败。

    【讨论】:

    • std::enable_if_t is C++14 and std::is_base_of_v` 是我想的 C++17 特性。我打算坚持使用 C++11(无缘无故)。这非常适用于 C++17(谢谢)。我能够将定义转换为 C++11:template &lt;typename Container, typename = typename std::enable_if&lt;!std::is_base_of&lt; A&lt;K,V&gt;, typename std::remove_reference&lt;Container&gt;::type &gt;::value&gt;::type&gt;。但这会阻止constfor 打印功能。有什么想法吗?
    • ` 模板, T>>> void print(const std::vector& z ) { for(auto & i : z) { print(i); } }` 这让const std::vector&lt;T&gt;&amp; zContainer 类型的加号是什么?
    猜你喜欢
    • 2017-08-22
    • 1970-01-01
    • 2021-08-20
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多