Reputation: 1278
I would like to design a template that automatically provides an operator<<(std::ostream&, const T&)
for all classes T
for which T::print_to(std::ostream&)
exists and can be called, so that I can define the printing function as a member function (and, in particular, take advantage of virtual calls).
Through trial and error, I managed to arrive at this:
template<typename T, typename = decltype(std::declval<T>().print_to(std::declval<std::ostream&>()))>
std::ostream &operator<<(std::ostream &s, const T &t) {
t.print_to(s);
return s;
}
It seems to be working, but since I am still new to SFINAE and this kind of tricks, would like to know if there is any pitfall or enhancement that can be made. I put a small test bench at https://ideone.com/uLJxac.
If possible, I would like to have a C++14 solution, because I am working with a C++14 code base. However, if using C++17 allows for a better solution, than I am also interested to that one.
Upvotes: 4
Views: 106
Reputation: 2748
Edit:
There is no pitfall with your code, sorry about that. But this answer enables you to write code more like C++20
concept
:
template <class T>
auto& operator << (std::ostream &out, const printable_t<T> &t)
{
t.print_to(out);
return out;
}
In fact, I wrote a C++17
concept_check library based on detector
and can be used in this way.
For more info on concept
support in C++20
, have a look at these 2:Constraints and concepts (since c++20) and Constraints and concepts (TS)
Original answer:
std::experiment::is_detector can do the magic for you. Though it is not in standard library, it is not difficult to implement and that link gives the suggested implementation.
Here I will give you how to detect that function along with my implementaion of is_detected_v
.
#include <type_traits>
#include <utility>
#include <ostream>
// For support for C++17 is not complete in many compiler, I also define void_t
template <class...> using void_t = void;
namespace impl {
template <class Default, class AlwaysVoid, template <class...> class Op, class ...Args>
struct detector: private std::false_type
{
using std::false_type::value;
using type = Default;
};
template <class Default, template <class...> class Op, class ...Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>: private std::true_type
{
using std::true_type::value;
using type = Op<Args...>;
};
} // namespace impl
struct nonsuch {};
#define CONCEPT_T constexpr const static inline bool
template <template<class...> class Op, class ...Args>
CONCEPT_T is_detected_v = impl::detector<nonsuch, void, Op, Args...>::value;
// Detect whether print_to exists.
template <class T>
using print_to_ret_t = decltype( std::declval<T>().print_to( std::declval<std::ostream&>() ) );
template <class T>
CONCEPT_T has_func_print_to_v = is_detected_v<print_to_ret_t, T>;
template <class T, std::enable_if_t< has_func_print_to_v<T> >>
using printable_t = T;
#undef CONCEPT_T
You can try to add C++14
support to this code. It won't be too difficult. The CONCEPT_T
must be changed to constexpr const static bool
to adjust to C++14
.
Upvotes: 1
Reputation: 66200
It seems to me that your applying SFINAE correctly in your operator<<()
; I don't see pitfalls in your solution.
I propose another version (C++11 compatible, so also C++14) just because require less typewriting
template <typename T>
auto operator<< (std::ostream & s, T const & t)
-> decltype( t.print_to(s), s )
{
t.print_to(s);
return s;
}
Upvotes: 5