2

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);
}
Sezen
  • 447
  • 1
  • 5
  • 17

3 Answers3

3

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); }
max66
  • 65,235
  • 10
  • 71
  • 111
  • How can I use `std::enable_if` to enable the template print function to operate only on objects derived from A? This promises. I'm appreciated if you can extend the answer with this. – Sezen May 31 '18 at 15:07
  • @Sezen - answer improved; hope this helps (but caution: code non tested). – max66 May 31 '18 at 15:45
  • 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 , T >::value>::type> void print(const std::vector& z) { for(auto& i:z) { print(i); } }` Can you edit the answer? – Sezen May 31 '18 at 16:57
  • @Sezen - I discourage the second-typename solution because can be "hijacked" explicating template paramenters; I mean: if you call `b5.print(std::vector{"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". – max66 May 31 '18 at 17:28
  • 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? – Sezen May 31 '18 at 17:32
  • @Sezen - not in this moment (sorry) but, in a couple of hour, I hope to be able to show you a compilable example. – max66 May 31 '18 at 18:13
  • 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 May 31 '18 at 18:33
  • The cause of the strange error was `void` in front of `print`. Thanks for the help. – Sezen May 31 '18 at 19:05
  • @Sezen - I see... a double `void`... one from `std::enable_if` and one explicit. – max66 May 31 '18 at 21:01
1

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.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
1

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.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • `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 std::remove_reference::type >::value>::type>`. But this prevents `const`for print function. Any idea? – Sezen May 31 '18 at 16:47
  • ` template, T>>> void print(const std::vector& z) { for(auto & i : z) { print(i); } }` This lets `const std::vector& z`. What is the plus of `Container` type? – Sezen May 31 '18 at 17:04