候选函数不可行:没有已知的从std :: vector <derived>到std :: vector <base />的转换

时间:2018-05-31 14:48:39

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

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

#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<B>std::vector<A<int,std::string>>的隐式转换,但事实并非如此。因此,我提出了两个解决方案。

  1. 定义typedef std::vector<A<int,std::string>> MyWeirdVector; 在A类中,使用se B::MyWeirdVector c;代替 std::vector<B> c;
  2. 在A类中将每个打印函数定义为template <typename U>,并接受typename U作为参数。
  3. 两种解决方案都有其自身的缺点。首先,我必须将c实例化为B :: MyWeirdVector,其次,我不(感觉)具有类型安全性。即使我没有在<>中定义类型,第二个解决方案也能正常工作。

    那么,这个问题有一个优雅的解决方案,就是让从std::vector<B>std::vector<A<int,std::string>>的隐式类型转换?

    - 编辑 -

    感谢@ max66和@Caleth以及其他人。我只想分享完整的工作实例。请注意,如果你不想发疯,那么在{max}的答案之前void之前没有print。 (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);
    }
    

3 个答案:

答案 0 :(得分:3)

怎么样?
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<B>std::vector<A<K, T>>的隐式转换,但您可以管理通用std::vector<T>(通用T)的内容并获取(在case)从T元素到A<K, T>的隐式转换(如果T是派生类型)。

如果需要,只有std::enable_if来自T,才能添加A<K, T>以启用模板打印功能。

- 编辑 -

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<>代替typename std::enable_if<>::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<>而不是`std :: is_base_of&lt;&gt; :: 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); }

答案 1 :(得分:1)

  

将每个打印功能定义为模板<typename U>

而不是这样,只定义使用typename抛出错误的print函数。

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

答案 2 :(得分:1)

为了最大限度的通用性:

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<K, V>的(可能是嵌套的)容器,则模板实例化将失败。