For education and for fun, you can do it this way (using C++11):
Code:
#include <type_traits>
#include <vector>
template<typename T>
auto foo(const T& printy) -> void
{
static_assert(!std::is_class<T>::value, "Type of argument printy must not be a class.");
bar(printy);
}
template<typename T>
auto bar(const T& printy) -> void
{
std::cout << printy << std::endl;
}
int main()
{
int x = 0;
std::vector<int> v;
foo(x); // Change to v and you get a compile error
return 0;
}
The reason I split foo into foo and bar is to prevent further compile errors from the compiler. You can put them together if you want.
Of course, this prevents you from passing in an object that supports operator <<. So if you conditionally want to detect if it supports being printed to an ostream (such as cout) with the << operator, you can do this (hang on to your boots!):
Code:
#include <type_traits>
#include <vector>
#include <utility>
template<typename T>
struct SupportsLeftStreamOperator
{
typedef char yes[1];
typedef char no[2];
template<typename U> static yes& check(decltype(std::cout << std::declval<U>())*);
template<typename U> static no& check(...);
static const bool value = (sizeof(check<T>(nullptr)) == sizeof(yes));
};
template<typename T>
auto foo(const T& printy) -> void
{
static_assert(!std::is_class<T>::value || SupportsLeftStreamOperator<T>::value, "Type of argument must support operator <<.");
bar(printy);
}
template<typename T>
auto bar(const T& printy) -> void
{
std::cout << printy << std::endl;
}
class test {};
std::ostream& operator << (std::ostream& out, const test& right) { return out; }
int main()
{
int x = 0;
test t;
std::vector<int> v;
foo(x);
foo(t);
//foo(v); // This will cause a compile error
return 0;
}
So how the heck does this work, you wonder? What kind of insane magic is this? Well, first of, read up on SFINAE. That's what this builds upon.
The idea is that SupportsLeftStreamOperator calls check which is a template function. It will take one argument - a pointer to the type of the expression you get when sending the object to std::cout (ie std::cout << myobj). If this isn't possible, ie the type does not an appropriate overload, the compiler will skip the function and take the next function which is a catch-all. The "..." argument list makes it possible to catch all types of types without fuss and ambiguity. Then I simply compare the size of the type the function call would have yielded, had it been called. sizeof does not actually call functions, so I'm okay here. I'm returning a reference to an array because that's allowed according to the standard, while returning a simple array is not. Check the documentation for std::declval, too.
This is tricky stuff and can take some time getting used to. Don't use it in production code. At least not until you can reproduce it and understand the implications and compiler limitations.
But if you just want to play around? Sure - go right ahead!