You can use is_detected for this. We're trying to check whether the given type is printable with one of std::cout
s overloads of operator<<
or if the type itself provides an operator<<
for printing to std::cout
. For a more general explanation of how I implemented this, check out https://www.fluentcpp.com/2017/06/02/write-template-metaprogramming-expressively/
First, we define an appropriate is_detected
for the overloads of std::cout itself:
// check std::cout.operator<<(T {})
template<typename = void, typename Arg = void> struct test_operator_of_cout : std::false_type {};
template<typename Arg>
struct test_operator_of_cout<std::void_t<decltype(std::cout.operator<<(std::declval<Arg>()))>, Arg>
: std::true_type {};
template<typename Arg>
constexpr bool test_operator_of_cout_v = test_operator_of_cout<void, Arg>::value;
And another one for all overloads of operator<<(ostream&, T {})
. The link I posted above generalizes this to have less code redundancy.
// check operator<<(std::cout, T {})
template<typename = void, typename Arg = void> struct test_operator_of_struct : std::false_type {};
template<typename Arg>
struct test_operator_of_struct<std::void_t<decltype(operator<<(std::cout, std::declval<Arg>()))>, Arg>
: std::true_type {};
template<typename Arg>
constexpr bool test_operator_of_struct_v = test_operator_of_struct<void, Arg>::value;
We can now use these type traits to implement the print function with enable_if:
template<typename T> struct MyClass {
T t;
template<
typename Consider = T,
typename = std::enable_if_t<
( test_operator_of_cout_v<Consider> || test_operator_of_struct_v<Consider>)
&& !std::is_arithmetic_v<Consider>
>
> void print() {
std::cout << t;
}
};
There are two things to note here:
- You need the first template argument of
Consider = T
. Otherwise, the compiler will try to instantiate the declaration of the function, which is ill-formed for types that do not fullfil the condition. Check out this SO-Answer for a more in-depth explanation: std::enable_if to conditionally compile a member function
- Arithmetic types are not printable because of the
!std::is_arithmetic
. I personally would not include this because I do not see a reason why my class shouldn't allow perfectly printable types to be printable.
Now we can look at what is printable and what not:
struct NotPrintable {};
struct Printable {
friend std::ostream& operator<<(std::ostream& os, const Printable& p) {
return os;
}
};
auto foo() {
MyClass<const char *> chars;
chars.print(); //compiles
MyClass<std::string> strings;
strings.print(); //compiles
MyClass<std::string_view> string_views;
string_views.print(); //compiles
MyClass<Printable> printables;
printables.print(); // compiles
// MyClass<int> ints;
// ints.print(); // Does not compile due to !is_arithmetiv_v
// MyClass<NotPrintable> not_printable;
// not_printable.print(); //Does not compile due to operator checking
}
You can check out the complete example here: https://godbolt.org/z/ZC9__e