candidate function not viable: no known conversion from std::vector<derived> to std::vector<base>

12,595

Solution 1

What about

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

instead of

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

?

I mean: you cannot have an implicit conversion from std::vector<B> to std::vector<A<K, T>> but you can manage the content of a generic std::vector<T> (generic T) and obtain (in case) implicit conversion from T elements to A<K, T> (if T is a derived type).

If you want, you can add an std::enable_if to enable the template print function only if T is derived from A<K, T>.

-- EDIT --

The OP asked

How can I use std::enable_if to enable the template print function to operate only on objects derived from A?

There are many ways; see, by example, the Caleth's answer with an additional template types and a std::enable_if to activate it.

But I prefer the returned value activated by std::enable_if.

Something as (caution: code not tested)

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

If you can use C++14 you can simplify a little (using std::enable_if_t<> instead of 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); }

and using C++17 a little more (std::is_base_of_v<> instead of `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); }

Solution 2

Define each print function as template <typename U>

Instead of this, do define only the print function that throws the error with the typename.

Since the two types are quite different, an implicit conversion wouldn't be an option, but my suggestion is.

Solution 3

For maximum generality:

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

This is type safe. If you try to pass something that isn't (potentially nested) containers of A<K, V>, the template instantiation will fail.

Share:
12,595
Sezen
Author by

Sezen

an ordinary coder!

Updated on June 05, 2022

Comments

  • Sezen
    Sezen almost 2 years

    First, The title probably may not reflect the current question, so please feel free to change. Assuming I have the following classes;

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

    I get the following error at last last line (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.
    

    I basically expect an implicit conversion from vector<B> to std::vector<A<int,std::string>> but it is not. Hence, I came up with two solutions to the issue.

    1. Define typedef std::vector<A<int,std::string>> MyWeirdVector; in class A and use se B::MyWeirdVector c; instead of std::vector<B> c;.
    2. Define each print function as template <typename U> in A class and accept typename U as argument.

    Both solutions has its own drawback. In first, I have to instantiate c as B::MyWeirdVector and in second, I don't (feel like) have a type safety. Second solutions works even if I don't define type in <>.

    So, is there an elegant solution to this issue like to let implicit type conversion from std::vector<B> to std::vector<A<int,std::string>>?

    -- EDIT --

    Thanks to @max66 and @Caleth and other fellows. I just want to share full working example. Please, note that there is no void before print for @max66's answer if you don't want to go crazy. (1. All print function arguments are const, 2. Merge answers from @max66 and @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);
    }
    
    • Admin
      Admin almost 6 years
    • Slava
      Slava almost 6 years
      It is a common problem, std::vector<A> and std::vector<B> are completely unrelated types even if A and B are related.
    • Slava
      Slava almost 6 years
      Print function should not be a member of A (can be static method or standalone maybe friend function) and it should accept const reference as parameter.
    • Caleth
      Caleth almost 6 years
      @Slava print is using members of the instance it is called on, as well as the members of it's parameter
    • Sezen
      Sezen almost 6 years
      @Slava why should not print be member of A? Especially, If it is designed to operate only on an object instantiated from A? Derived B class might have another variable, let's say double m but I don't care. - Yes, as @Caleth said.
    • Slava
      Slava almost 6 years
      @Sezen I missed that it uses object data, yea in this case it is fine, but it should be const method that accepts const reference.
    • Sezen
      Sezen almost 6 years
      I agree with const. I was a bit careless while creating minimal reproducible example. Thanks ^_^.
    • Slava
      Slava almost 6 years
      Note: if your print vector would work somehow it would have slicing issue, as you pass vector by value.
  • Sezen
    Sezen almost 6 years
    How can I use std::enable_if to enable the template print function to operate only on objects derived from A<K,T>? This promises. I'm appreciated if you can extend the answer with this.
  • max66
    max66 almost 6 years
    @Sezen - answer improved; hope this helps (but caution: code non tested).
  • Sezen
    Sezen almost 6 years
    std::enable_if_t is C++14 and std::is_base_of_v` is C++17 feature I suppose. I plan to stick to C++11 (for no reason). This works for C++17 very well (thanks). I was able to convert the definition to C++11: template <typename Container, typename = typename std::enable_if<!std::is_base_of< A<K,V>, typename std::remove_reference<Container>::type >::value>::type>. But this prevents constfor print function. Any idea?
  • Sezen
    Sezen almost 6 years
    Thanks @max66. I was able to convert the code to C++11 and test by the help of you and @Caleth. Here it is: ` template <typename T, typename = typename std::enable_if <std::is_base_of< A<K,V>, T >::value>::type> void print(const std::vector<T>& z) { for(auto& i:z) { print(i); } }` Can you edit the answer?
  • Sezen
    Sezen almost 6 years
    ` template<typename T, typename = std::enable_if_t<std::is_base_of_v<A<K, V>, T>>> void print(const std::vector<T>& z) { for(auto & i : z) { print(i); } }` This lets const std::vector<T>& z. What is the plus of Container type?
  • max66
    max66 almost 6 years
    @Sezen - I discourage the second-typename solution because can be "hijacked" explicating template paramenters; I mean: if you call b5.print<std::string, void>(std::vector<std::string>{"abc", "123"}), you activate the method (you get an error calling the other print() obviously); using std::enable_if over the returned value, the method can't be "hijacked".
  • Sezen
    Sezen almost 6 years
    I couldn't make your example work, so, I mixed your and @Caleth's answer somehow :). Can you give a tested and working example without second-typename solution?
  • max66
    max66 almost 6 years
    @Sezen - not in this moment (sorry) but, in a couple of hour, I hope to be able to show you a compilable example.
  • Sezen
    Sezen almost 6 years
    Sorry; it works, but I couldn't make it work on original code. I'm getting cannot combine with previous 'type-name' declaration specifier error but "hijackable" version works. Interesting!
  • Sezen
    Sezen almost 6 years
    The cause of the strange error was void in front of print. Thanks for the help.
  • max66
    max66 almost 6 years
    @Sezen - I see... a double void... one from std::enable_if and one explicit.