I would like to have operator>>()
overloads for any "base" type and for any container type. This is what I have so far:
typedef uintmax_t my_t;
template <typename T>
std::istringstream &operator>>(std::istringstream &iss, T &v)
{
static my_t um = 6009;
v = um++;
return iss;
}
template <template <class> class C, typename T>
std::istringstream &operator>>(std::istringstream &iss, C<T> &c)
{
for (typename C<T>::iterator it = c.begin(); it != c.end(); ++it)
iss >> *it;
return iss;
}
int main()
{
std::vector<uint32_t> vi(3);
std::istringstream iss;
iss >> vi;
for (std::vector<uint32_t>::iterator it = vi.begin(); it != vi.end(); ++it)
std::cout << *it << std::endl;
}
This compiles and runs as expected with GCC but does not even compile on VS2015. The latter matches the >>
operator in the iss >> vi;
statement with the first, base-type overload, which triggers other compilation errors. How can I write an operator>>()
template for non-container types and a template for container types (without having to specialize for each container type) that compiles with GCC and VS2015?
The operator>>
overload you wrote is for a template template class (C
) with a single template argument (T
). However, std::vector
is declared as:
template<
class T,
class Allocator = std::allocator<T>
> class vector;
The second template argument may be defaulted, but it is still there. As such, std::vector<uint32_t>
cannot match C<T>
- so the only viable overload is the generic function template that you wrote, which won't compile because you can't assign a std::uintmax_t
to a vector
.
In order to get your function to accept vector
, you need to match the template template declaration - which means, take a second type argument:
template <template <class, class> class C, typename T1, typename T2>
std::istringstream &operator>>(std::istringstream &iss, C<T1,T2> &c)
{
for (typename C<T1,T2>::iterator it = c.begin(); it != c.end(); ++it)
iss >> *it;
return iss;
}
This is a pretty unsatisfactory solution though. Really, we want to match on anything that is a container, which we can do using SFINAE. Since this is C++03, the simplest thing would be to write a type trait for whether or not some type has a typedef named iterator
:
template <typename T>
struct is_container {
typedef char yes;
struct no {
char _[2];
};
template <typename U>
static yes test( typename U::iterator* );
template <typename U>
static no test(...);
static const bool value = (sizeof(test<T>(0)) == sizeof(yes));
};
And add our handy enable_if
:
template <bool, typename >
struct enable_if { };
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
And stick that on the return type:
template <typename C>
typename enable_if<
is_container<C>::value,
std::istringstream&
>::type
operator>>(std::istringstream &iss, C& c)
{
for (typename C::iterator it = c.begin(); it != c.end(); ++it)
iss >> *it;
return iss;
}
You'll have to do the opposite (!is_container<T>::value
) for the other overload so that they're not ambiguous.