Giovanni Mascellani
Giovanni Mascellani

Reputation: 1278

Enable a template depending on whether a certain function exists

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

Answers (2)

JiaHao Xu
JiaHao Xu

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_Tmust be changed to constexpr const static bool to adjust to C++14.

Upvotes: 1

max66
max66

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

Related Questions