0

I am using this magical header to gain the ability to easily serialize STL containers.

However, I have now moved on to even more fancy HTML serializers for my types, and part of what I would like to do is generalize the operator << functionality to my new type ohtmlstringstream which is backed by a stringstream.

Here is my (functioning) attempt to do this (ohtmlstringstream::write is a public template method that passes its arg on to the private member stringstream's operator<<):

namespace std {
    template<typename T>
    inline typename enable_if< ::pretty_print::is_container<T>::value, ohtmlstringstream&>::type
    operator<<(ohtmlstringstream& os, const T& container) {
        auto it = std::begin(container);
        const auto the_end = end(container);
        os.write("<div class='container'>");
        while(it != the_end) {
            os << *it;
            it++;
        }
        os.write("</div>");
        return os;
    }
}

The first problem that I run into is that any time a std::string is used on ohtmlstringstream, it is treated as a container, which is something that I do not want; I'd like to treat strings as just strings, not as containers. Of course, as far as pretty_print is concerned, the std::string is most certainly a container of chars.

This is an excerpt from prettyprint.hpp:

namespace pretty_print
{

    // SFINAE type trait to detect whether T::const_iterator exists.

    template<typename T>
    struct has_const_iterator
    {
    private:
        typedef char                      yes;
        typedef struct { char array[2]; } no;

        template <typename C> static yes test(typename C::const_iterator*);
        template <typename C> static no  test(...);
    public:
        static const bool value = sizeof(test<T>(0)) == sizeof(yes);
        typedef T type;
    };

    // SFINAE type trait to detect whether "T::const_iterator T::begin/end() const" exist.

    template <typename T>
    struct has_begin_end_OLD
    {
        struct Dummy { typedef void const_iterator; };
        typedef typename std::conditional<has_const_iterator<T>::value, T, Dummy>::type TType;
        typedef typename TType::const_iterator iter;

        struct Fallback { iter begin() const; iter end() const; };
        struct Derived : TType, Fallback { };

        template<typename C, C> struct ChT;

        template<typename C> static char (&f(ChT<iter (Fallback::*)() const, &C::begin>*))[1];
        template<typename C> static char (&f(...))[2];
        template<typename C> static char (&g(ChT<iter (Fallback::*)() const, &C::end>*))[1];
        template<typename C> static char (&g(...))[2];

        static bool const beg_value = sizeof(f<Derived>(0)) == 2;
        static bool const end_value = sizeof(g<Derived>(0)) == 2;
    };

    template <typename T>
    struct has_begin_end
    {
        template<typename C> static char (&f(typename std::enable_if<
                                             std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::begin)),
                                             typename C::const_iterator(C::*)() const>::value, void>::type*))[1];

        template<typename C> static char (&f(...))[2];

        template<typename C> static char (&g(typename std::enable_if<
                                             std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::end)),
                                             typename C::const_iterator(C::*)() const>::value, void>::type*))[1];

        template<typename C> static char (&g(...))[2];

        static bool const beg_value = sizeof(f<T>(0)) == 1;
        static bool const end_value = sizeof(g<T>(0)) == 1;
    };

    // Basic is_container template; specialize to derive from std::true_type for all desired container types

    template<typename T> struct is_container : public ::std::integral_constant<bool,
    has_const_iterator<T>::value && has_begin_end<T>::beg_value && has_begin_end<T>::end_value> { };

    template<typename T, std::size_t N> struct is_container<T[N]> : public ::std::true_type { };

    template<std::size_t N> struct is_container<char[N]> : public ::std::false_type { };

    template <typename T> struct is_container< ::std::valarray<T>> : public ::std::true_type { }; 

...<snip>

The problem here is that it's not clear to me how I could use SFINAE and enable_if and the rest of this stuff to build yet another predicate that evalutes to true for all containers except std::string.

Now, that's only the first problem. The second problem is the line in my first code listing that goes os.write("<div class='container'>");. Note how annoyingly unspecific this is. I really would like the serialization routine of the container to report the actual type of the container (be it std::map or std::forward-list or std::vector).

What I'd like to know is whether there exists some (reasonably sane) method to achieve this with templates, or whether I really should just use macros to explicitly define a series of templates, one for each STL container type: That way I can easily build the exact kind of HTML I want for any given container.

It is true that enumerating all of the STL containers with templates would solve both problems. I think I'll start doing this. But I'd still like to know the answer to the original first question. How can I exclude a particular type using enable_if?

Community
  • 1
  • 1
Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • `I'd like to treat strings as just strings, not as containers` What – Lightness Races in Orbit Jan 06 '14 at 02:33
  • `::pretty_print::is_container::value` == `true`. It has a `const_iterator`, it has begin and end. However I'd sort of also like to know what on earth `has_begin_end` is actually doing and why it works. – Steven Lu Jan 06 '14 at 02:35
  • @StevenLu, Not being as good on the SFINAE part, I can tell you `f` is a function that returns a reference to an array of N characters. It uses SFINAE in the type of the parameter. I guess it works the same way that the complicated one isn't considered in the call at the bottom if the member doesn't exist. I'm not sure if it has to be in the parameter, but a `decltype` [in the return type](http://stackoverflow.com/questions/16762263/what-does-after-a-function-prototype-mean) is more readable imo. I guess this one also makes sure it's a function of the right type as well. – chris Jan 06 '14 at 03:01
  • 3
    You don't need to, and strictly aren't allowed to, declare an `operator<<` overload in `namespace std`. – Potatoswatter Jan 06 '14 at 03:05

1 Answers1

0

Add &&!std::is_same<Foo, std::string>::value to your enable_if test.

The syntax of enable_if< condition , optional type >::type may help -- the condition is any compile time bool. You can just toss more stuff in there!

If you want one trait for reuse, just create one that inherits from std::integral_constant<bool, logic here >{};.

If you have full C++11 support, try writing a constexpr function takes the type T and returns true instead of a traits class. In C++1y this may be useful when concepts lite arrives.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Any tricks for dealing with the second problem? I guess I could write more partial template specializations or something? – Steven Lu Jan 06 '14 at 03:04
  • @stevenlu just write a quick traits class that has a static method that returns the cotainer name. Either leave the unspecialized trait unimplrmented to get errors, or give it a generic name. This is orthogonal to the rest of the code... – Yakk - Adam Nevraumont Jan 06 '14 at 03:21