candidate function not viable: no known conversion from std::vector<derived> to std::vector<base>
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.
Comments
-
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>
tostd::vector<A<int,std::string>>
but it is not. Hence, I came up with two solutions to the issue.- Define
typedef std::vector<A<int,std::string>> MyWeirdVector;
in class A and use seB::MyWeirdVector c;
instead ofstd::vector<B> c;
. - 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>
tostd::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
beforeprint
for @max66's answer if you don't want to go crazy. (1. All print function arguments areconst
, 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 almost 6 yearsIs this related? stackoverflow.com/questions/13403490/…
-
Slava almost 6 yearsIt is a common problem,
std::vector<A>
andstd::vector<B>
are completely unrelated types even ifA
andB
are related. -
Slava almost 6 yearsPrint 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 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 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 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 almost 6 yearsI agree with const. I was a bit careless while creating minimal reproducible example. Thanks ^_^.
-
Slava almost 6 yearsNote: if your print vector would work somehow it would have slicing issue, as you pass vector by value.
- Define
-
Sezen almost 6 yearsHow 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 almost 6 years@Sezen - answer improved; hope this helps (but caution: code non tested).
-
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 preventsconst
for print function. Any idea? -
Sezen almost 6 yearsThanks @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 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 ofContainer
type? -
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 otherprint()
obviously); usingstd::enable_if
over the returned value, the method can't be "hijacked". -
Sezen almost 6 yearsI 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 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 almost 6 yearsSorry; 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 almost 6 yearsThe cause of the strange error was
void
in front ofprint
. Thanks for the help. -
max66 almost 6 years@Sezen - I see... a double
void
... one fromstd::enable_if
and one explicit.