Refactor stringification (#585)

* matcher-like nan check

* Remove superfluous extern template declarations

* Add explicit template parameters

* Correct template instantiation

* Fix test includes

* class -> struct

* Correctly instantiate

* Oops

* Try fix interface

* Add MinGW exception

* Add info regarding interface decl and def

* Adjust docs

* Remove accidental paste in comment

* First draft

* operator<< => StringStream (for now)

* Forward declare cstr output operator

* Remove unnecessary String constructor

* Port more stuff to streams

* Remove std::string stringification (it was broken anyways)

* Remove anonymous namespace for the time being

* Revert "Remove anonymous namespace for the time being"

This reverts commit ec2819c44bdb647546108d29b135720083ded48c.

* Move toStream to prevent disabling

* Restore customization points

* Remove superfluous const char* catcher

* Merge branch 'dev' into fix-string

* Better IsNaN stringification

* Reset doctest

* We're getting somewhere!

* size_t -> unsigned long

* Fix nullptr handling

* Why is it selecting the template over the overload??

* Reduce template count

* Forward declare cstr output operator (again)

* Fix pointer stringification

* Add flag that forces custom stringification methods to be provided (#595)

* Add flag that forces custom stringification methods to be provided

* Add docs

* Add IsNaN operator! (#603)

* Add IsNaN operator!

* Docs

* More concise impl

* Optimized floating point stringification

* Remove float stringification override

* unsigned long -> size_t where appropriate

* Automatic type stringification with optional overrides

* Fix type stringification

* Add manual short override to fix tests

* Add tests

* insertion fix?

* Make operator<< static

* Clean up fake type traits

* Try fix stl warnings

* Reintroduce deferred_false

* Work around dumb VS15 shit

* Oops

* Yet another MSVS2015 workaround

* Fix #618

* Doing ungodly things to make MSVS2015 work

* Oops

* rerun tests

* Rerun tests

* Fix #618 by removing string_view

* Remove incorrect restrictions on <string> inclusion

* Add String::EMPTY

* Replace String::EMPTY with static EMPTY_STRING in order to avoid SIOF

* Revert "Add String::EMPTY"

This reverts commit 8856a220596398f27e11a031cedda352f067cbf8.

Revert "Replace String::EMPTY with static EMPTY_STRING in order to avoid SIOF"

This reverts commit 83d3c4f45dde09038d13e77379ea3b40843ce37f.
diff --git a/examples/all_features/stringification.cpp b/examples/all_features/stringification.cpp
index 2add560..aa3167d 100644
--- a/examples/all_features/stringification.cpp
+++ b/examples/all_features/stringification.cpp
@@ -1,7 +1,74 @@
+#ifdef _MSC_VER
+__pragma(warning(push))
+__pragma(warning(disable : 4643))
+namespace std {
+    template <typename> struct char_traits;
+    template <typename, typename> class basic_ostream;
+    typedef basic_ostream<char, char_traits<char>> ostream;
+    template<class TRAITS>
+    basic_ostream<char, TRAITS>& operator<<(basic_ostream<char, TRAITS>&, const char*);
+}
+__pragma(warning(pop))
+#else
+#include <iostream>
+#endif
+
+namespace N {
+    struct A { };
+    struct B {
+        friend std::ostream& operator<<(std::ostream& os, const B&) { return os << "B"; }
+    };
+    struct C { };
+    static std::ostream& operator<<(std::ostream& os, const C&) { return os << "C"; }
+}
+
+static std::ostream& operator<<(std::ostream& os, const N::A&) { return os << "A"; }
+
 #include <doctest/doctest.h>
 
+#include <utility>
+
+TEST_CASE("operator<<") {
+    MESSAGE(N::A{ });
+    MESSAGE(N::B{ });
+    MESSAGE(N::C{ });
+}
+
 #include "header.h"
 
+// std::move is broken with VS <= 15
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+#define MOVE(...) __VA_ARGS__
+#else
+#define MOVE std::move
+#endif
+
+TEST_CASE("no headers") {
+    char chs[] = { '1', 'a', 's' };
+    MESSAGE(chs); CHECK(chs == nullptr);
+    MESSAGE("1as"); CHECK("1as" == nullptr);
+
+    int ints[] = { 0, 1, 1, 2, 3, 5, 8, 13 };
+    MESSAGE(ints); CHECK(ints == nullptr);
+    MESSAGE(MOVE(ints));
+
+    char* cptr = reinterpret_cast<char*>(ints + 4);
+    const char* ccptr = const_cast<const char*>(cptr);
+    void* vptr = reinterpret_cast<void*>(cptr);
+    CHECK(doctest::toString(cptr) == doctest::toString(ccptr));
+    CHECK(doctest::toString(ccptr) == doctest::toString(vptr));
+
+    char* cnptr = nullptr;
+    MESSAGE(cnptr); CHECK(cnptr != nullptr);
+
+    enum Test {
+        A = 0, B, C = 100,
+    };
+    MESSAGE(A); CHECK(A == C);
+
+    MESSAGE(doctest::toString<int>());
+}
+
 DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #include <string>
 #include <vector>
@@ -18,11 +85,10 @@
 template <typename T>
 ostream& operator<<(ostream& stream, const vector<T>& in) {
     stream << "[";
-    for(size_t i = 0; i < in.size(); ++i)
-        if(i < in.size() - 1)
-            stream << in[i] << ", ";
-        else
-            stream << in[i];
+    for (size_t i = 0; i < in.size(); ++i) {
+        if (i != 0) { stream << ", "; }
+        stream << in[i];
+    }
     stream << "]";
     return stream;
 }
@@ -32,16 +98,17 @@
 namespace doctest
 {
 template <typename T>
-struct StringMaker<std::list<T> >
+struct StringMaker<std::list<T>>
 {
     static String convert(const std::list<T>& in) {
         std::ostringstream oss;
 
         oss << "[";
-        for(typename std::list<T>::const_iterator it = in.begin(); it != in.end(); ++it)
-            oss << *it << ", ";
+        for (typename std::list<T>::const_iterator it = in.begin(); it != in.end();) {
+            oss << *it;
+            if (++it != in.end()) { oss << ", "; }
+        }
         oss << "]";
-
         return oss.str().c_str();
     }
 };
@@ -100,18 +167,23 @@
            doctest::toString(ex.two) + ")";
 }
 
+#define CHECK_NOT_DEFAULT_STR(var) CHECK(toString(var) != "{?}")
+
 TEST_CASE("all asserts should fail and show how the objects get stringified") {
     MyTypeInherited<int> bla1;
     bla1.one = 5;
     bla1.two = 4u;
 
     Bar::Foo f1;
+    MESSAGE(f1);
     Bar::Foo f2;
     CHECK(f1 == f2);
 
     // std::string already has an operator<< working with std::ostream
     std::string dummy = "omg";
 
+    MESSAGE(dummy);
+
     CHECK(dummy == "tralala"); // should fail
     CHECK("tralala" == dummy); // should fail
 
@@ -120,6 +192,8 @@
     vec1.push_back(2);
     vec1.push_back(3);
 
+    MESSAGE(vec1);
+
     std::vector<int> vec2;
     vec2.push_back(1);
     vec2.push_back(2);
@@ -132,6 +206,8 @@
     lst_1.push_back(42);
     lst_1.push_back(3);
 
+    MESSAGE(lst_1);
+
     std::list<int> lst_2;
     lst_2.push_back(1);
     lst_2.push_back(2);
@@ -147,9 +223,9 @@
         CHECK_MESSAGE(s1 == s2, s1, " is not really ", s2);
     }
 
-    CHECK(doctest::IsNaN<double>(0.5));
-    CHECK(doctest::IsNaN<float>(std::numeric_limits<float>::infinity()));
-    // can't test actual nan because it's implementation defined
+    CHECK_NOT_DEFAULT_STR(doctest::IsNaN<double>(0.5));
+    CHECK_NOT_DEFAULT_STR(!doctest::IsNaN<float>(std::numeric_limits<float>::infinity()));
+    CHECK_NOT_DEFAULT_STR(doctest::IsNaN<double long>(std::numeric_limits<double long>::quiet_NaN()));
 
     CHECK("a" == doctest::Contains("aaa"));