8

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;
}
Antoine Morrier
  • 3,930
  • 16
  • 37
  • `std::enable_if_t` didn't exist until C++14, so is your code really C++11? – AndyG Jul 13 '17 at 16:46
  • 1
    it is possible to implement it in C++11, so it is possible to answer this question with C++11 as well. However, yes, the code is C++14 (even C++1z with Clang) – Antoine Morrier Jul 13 '17 at 16:49
  • 1
    Not related to your direct question, but why are you bothering with `...Args` at all? Your function would work fine as a simple `template` –  Jul 13 '17 at 17:03
  • Possible duplicate of [Is it possible to write a template to check for a function's existence?](https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence) –  Jul 13 '17 at 17:05
  • @Frank because containers have multiple template arguments : one for the type, but other for allocator, keys (for map or other) etc etc – Antoine Morrier Jul 13 '17 at 17:09
  • I don't think this should be possible. If you think of it - it would mean that the function should exist only if function does. But then the function should not exist as it exists... – W.F. Jul 13 '17 at 17:10
  • 1
    @Antoine Morrier but `template` matches *any* type, templated or not: https://pastebin.com/jqYBsvRK –  Jul 13 '17 at 17:11
  • @Frank it is a good question, I will see if it is possible or if there is problem with that :p. Thanks for this advice :) Yes you are right – Antoine Morrier Jul 13 '17 at 17:13
  • Possibly the same question as https://stackoverflow.com/questions/15912283/how-to-avoid-this-sentence-is-false-in-a-template-sfinae which also provides a clever solution that avoids your problem entirely. As an aside, I find overloading operators on types you do not own to be impolite (bad form), so I wouldn't do this: what happens when C++20 adds `<<` to vector in a different format? Doing so outside the type's namespace is even worse, as now it only works in the namespace where you overloaded `<<`. – Yakk - Adam Nevraumont Jul 13 '17 at 18:02
  • I'd consider doing a `std::cout << stream_range( v, ',' );` instead, where `stream_range` takes an iterable range and streams it separated by `,`. – Yakk - Adam Nevraumont Jul 13 '17 at 18:05

2 Answers2

4

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

AndyG
  • 39,700
  • 8
  • 109
  • 143
  • 1
    That is what I tried with the "is_streamable out" mentioned in my question and it does not work on Clang. Only on MSVC. – Antoine Morrier Jul 13 '17 at 17:38
  • The problem is use `can_print_v()` to sfinae enable the definition of `operator<<()` when `can_print_v()` is false; with a similar solution my compiler goes in loop. – max66 Jul 13 '17 at 17:38
  • @max66: Updated with fuller example. – AndyG Jul 13 '17 at 17:47
  • @AntoineMorrier: Can you elaborate? I was using clang in my demo. – AndyG Jul 13 '17 at 17:54
  • I am using the same solution as yours or almost : `std::enable_if_t && !can_print_v, std::ostream> &operator<<(std::ostream &out, T const &t)`. When I try to read a std::string, there is no problem now. But when I am using a std::vector<> there is one. Because clang seems to find the function even if it is not built yet. – Antoine Morrier Jul 13 '17 at 17:58
  • I add a full code in my question I am using clang C2 (inside visual studio) – Antoine Morrier Jul 13 '17 at 18:01
  • my g++ (6.3.0) compile your example but from clang++ (3.8.1) I get the following error "fatal error: recursive template instantiation exceeded maximum depth of 256"; same error that I get from a similar solution based on constexpr functions. I don't know how right (g++ or clang++) – max66 Jul 13 '17 at 18:44
  • @max66: It looks like it only works on clang 3.9.1 and up. It appears that this has to do with the contradiction of using `!can_print_v` to SFINAE a function that would make `!can_print_v` `false`. – AndyG Jul 13 '17 at 18:49
  • You're right: starting from 3.9.1 works my solution too. Interesting. – max66 Jul 13 '17 at 19:11
4

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    The "This sentence is false" was just blowing my mind the more I thought about my answer. I think OP wants the nice `stream << T` syntax, but we can't get it with SFINAE without ending up in this true/false scenario. – AndyG Jul 13 '17 at 18:19