SFINAE: Know if a function exists or not

Basically, I want to write code like this:

std::vector<float> a = { 54, 25, 32.5 }; std::vector<int> b = { 55, 65, 6 }; std::cout << a << b << std::string("lol"); 

This is not possible because there is no overload for operator<<(ostream&, vector)

So, I am writing a function that does the job:

 template<template<typename...> typename T, typename ...Args> std::enable_if_t<is_iterable_v<T<Args...>>>, std::ostream> &operator<<(std::ostream &out, T<Args...> const &t) { for (auto const &e : t) out << e << " "; out << std::endl; return out; } 

This works well, but I have a string problem. Since the strings are iterable and the strings have a HAVE operator<< function.

So, I tested with another sign, for example !is_streamable_out && _is_iterable , testing something like this: std::declval<std::ostream&>() << std::declval<T>() and if it has begin functions / end. It works well on MSVC, but not on Clang (I think this is because the compiler uses the function just created, so it found one overload available for all methods).

So, I am currently using !is_same_v<string, T> , but it is not perfect IMHO.

Is there a way to find out if a function exists without updating the function?

Basically, I want to do something like this

 if function foo does not exist for this type. then function foo for this type is ... 

This is not the same problem as Is it possible to write a template to verify the existence of a function? since in this other thread the function is not exactly (toString vs toOptionalString). In my case, the function is the same.

Here is my complete code:

 template <class...> struct make_void { using type = void; }; template <typename... T> using void_t = typename make_void<T...>::type; // force SFINAE namespace detail { template<typename AlwaysVoid, template<typename...> typename Operator, typename ...Args> struct _is_valid : std::false_type {}; template<template<typename...> typename Operator, typename ...Args> struct _is_valid<void_t<Operator<Args...>>, Operator, Args...> : std::true_type { using type = Operator<Args...>; }; } template<template<typename ...> typename Operator, typename ...Args> using is_valid = detail::_is_valid<void, Operator, Args...>; #define HAS_MEMBER(name, ...)\ template<typename T>\ using _has_##name = decltype(std::declval<T>().name(__VA_ARGS__));\ \ template<typename T>\ using has_##name = is_valid<_has_push_back, T>;\ \ template<typename T>\ constexpr bool has_##name##_v = has_##name<T>::value HAS_MEMBER(push_back, std::declval<typename T::value_type>()); HAS_MEMBER(begin); HAS_MEMBER(end); template<typename T> using is_iterable = std::conditional_t<has_begin_v<T> && has_end_v<T>, std::true_type, std::false_type>; template<typename T> constexpr bool is_iterable_v = is_iterable<T>::value; template<class T, class Stream, class = void> struct can_print : std::false_type {}; template<class T, class Stream> struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type {}; template<class T, class Stream = std::ostream> constexpr bool can_print_v = can_print<T, Stream>::value; template<typename T> std::enable_if_t<is_iterable_v<T> && !can_print_v<T>, std::ostream> &operator<<(std::ostream &out, T const &t) { for (auto const &e : t) out << e << " "; out << std::endl; return out; } template<typename A, typename B> std::ostream &operator<<(std::ostream &out, std::pair<A, B> const &p) { out << p.first << " " << p.second << std::endl; return out; } template<typename T> std::enable_if_t<has_push_back_v<T>, T> &operator<<(T &c, typename T::value_type const &e) { c.push_back(e); return c; } 

and main:

 #include <iostream> #include <vector> #include "Tools/stream.h" #include <string> #include <map> int main() { std::vector<float> a = { 54, 25, 32.5 }; std::vector<int> b = { 55, 65, 6 }; std::cout << a; std::cout << has_push_back<float>::value << " " << has_push_back<std::vector<float>>::value << std::endl; std::cout << is_iterable<std::vector<float>>::value << " " << is_iterable<float>::value << std::endl; getchar(); return 0; } 
+8
c ++ c ++ 11 sfinae
source share
2 answers

How to avoid this sentence is false in the SFINAE template? gives an answer that solves your problem - overload <<(ostream&, Ts...) , which will be found with a lower priority than any other overload << .

At the same time, I would say that your plan is bad. Overload operators for std types are a bad plan for two reasons.

First, you should not overload operators for types that you do not own unless there is a big reason.

Secondly, if you do this, you must do this in the type namespace, and you cannot enter << into the namespace std without making your program poorly formed.

Operators overloaded in a namespace other than the types in question can only be found in the namespace in which you overloaded. Operators overloaded in the namespace associated with arguments can be found anywhere.

This leads to the fragility of << , which only works in one namespace.


So do the following:

 struct print_nothing_t {}; inline std::ostream& operator<<(std::ostream& os, print_nothing_t) { return os; } template<class C, class Sep=print_nothing_t> struct stream_range_t { C&& c; Sep s; template<class T=print_nothing_t> stream_range_t( C&& cin, T&& sin = {} ): c(std::forward<C>(cin)), s(std::forward<T>(sin)) {} friend std::ostream& operator<<(std::ostream& os, stream_range_t&& self) { bool first = true; for (auto&& x:self.c) { if (!first) os << self.s; os << decltype(x)(x); first = false; } return os; } }; template<class C, class Sep = print_nothing_t> stream_range_t<C, Sep> stream_range( C&& c, Sep&& s={} ) { return {std::forward<C>(c), std::forward<Sep>(s)}; } 

Now, if you want nested vectors to work, you need to implement the stream range that the adapter applies to its contents.

Real-time example using this test code:

 std::vector<int> v{1,2,3,4}; std::cout << stream_range(v, ',') << "\n"; 

conclusion 1,2,3,4 .


How to make it recursive:

We can write the adapt_for_streaming function, which sends either to identity (for things that are already streaming) and for stream_range for things that can be iterable but not cloudy anymore.

Users can then add new adapt_for_streaming overloads for other types.

stream_range_t , then the stream using adapt_for_streaming in its contents.

Then we can add the tuple / pair / array overloads to adapt_for_streaming , and suddenly std::vector< std::vector< std::tuple<std::string, int> > > adapt_for_streaming can be passed.

End users can directly call stream_range or adapt_for_streaming to associate the container with iteration. You can even call stream_range directly on std::string to treat it as a stream set of char instead of a string.

+4
source share

You can write a small discovery idiom that checks if the expression stream << value * is correctly expressed.

This uses std::ostream :

 template<class...> using void_t = void; template<class T, class Stream, class=void> struct can_print : std::false_type{}; template<class T, class Stream> struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type{}; template<class T, class Stream=std::ostream> constexpr bool can_print_v = can_print<T, Stream>::value; 

Now you can write your overload for operator<< as follows:

 template<template<class...> class C, class...T> std::enable_if_t<!can_print_v<C<T...>>, std::ostream>& operator<<(std::ostream &out, C<T...> const &t) { for (auto const &e : t) out << e << " "; out << std::endl; return out; } 

and test

 std::vector<float> a = { 54, 25, 32.5 }; std::vector<int> b = { 55, 65, 6 }; std::cout << a; std::cout << b; std::cout << std::string("lol") << std::endl; 

Demo


Yakk points out an interesting thing in their answer . This sentence is incorrect .

Basically, using !can_print_v to enable overloading for operator<< , can_print_v should then be true , but it is false because the first time the template was created, the structure was deduced from std::false_type . Therefore, subsequent tests for can_print_v not valid.

I leave this answer as a warning story. The trait itself is okay, but using it for SFINAE is something invalid, the trait is not okay.

* It seems like the OP has copied this code into its own codebase and then modified the question to include it, in case you are wondering why it looks the other way around.

+4
source share

All Articles