Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE : Know if a function already exist or no

Tags:

c++

c++11

sfinae

Basically, I want to write code like that :

std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };

std::cout << a << b << std::string("lol");

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

So, I write a function that do 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;
}

That works good, but I have a problem with string. Because strings are iterable and strings HAVE operator<< function.

So I tested with another trait like !is_streamable_out && _is_iterable testing something like that : std::declval<std::ostream&>() << std::declval<T>() and if it has begin / end functions. It works good on MSVC, but not on Clang (I think it is because the compiler use the function I just create as well, so it founds 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 know if a function exists without redeclaring the function?

Basically, I want to do something like that

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

It is not the same problem as Is it possible to write a template to check for a function's existence? because in this other thread, the function is not exactyly the same (toString vs toOptionalString). In my case, the function are the same.

Here is my full 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 the 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;
}
like image 751
Antoine Morrier Avatar asked Jul 13 '17 16:07

Antoine Morrier


2 Answers

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

At the same time I'd say your plan is a poor one. Overloading operators for std types is a bad plan for 2 reasons.

First, you should be reluctant to overload operators for types you do not own unless there is a great reason.

Second, if you do so, you should do it in the namespace of the type, and you cannot inject your << into namespace std without making your program ill-formed.

Operators overloaded in a namespace different than the types in question can only be found in the namespace where you did the overload. Operators overloaded in a namespace related to the arguments can be found anywhere.

This results in fragile << that only works in one namespace.


So, instead, do this:

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'd have to implement a stream range that applies an adapter to its contents.

Live example with this test code:

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

output is 1,2,3,4.


How to make this recursive:

We can write a adapt_for_streaming function that dispatches to either identity (for things already streamable), and to stream_range for things iterable but not already streamable.

Users can then add new adapt_for_streaming overloads for other types.

stream_range_t then streams using adapt_for_streaming on its contents.

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

End users can call stream_range directly, or adapt_for_streaming, to string an iterable container. You can even call stream_range directly on a std::string to treat it like a streamable collection of char instead of a string.

like image 92
Yakk - Adam Nevraumont Avatar answered Nov 14 '22 20:11

Yakk - Adam Nevraumont


You could write a small detection idiom that tests if the expression stream << value is well formed*.

Here it is using 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 your can write your overload for operator<< like so:

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 a 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 false.

Basically, by using !can_print_v to enable an overload for operator<<, the can_print_v should subsequently be true, but it's false because the first instantiation of the template resulted in the struct deriving from std::false_type. Subsequent tests for can_print_v are therefore invalid.

I'm leaving this answer as a cautionary tale. The trait itself is okay, but using it to SFINAE something that invalidates the trait is not okay.

*It appears OP has copied this code into their own codebase and then modified the question to include it, in case you were wondering why it looks the other way around

like image 40
AndyG Avatar answered Nov 14 '22 19:11

AndyG