#include <cstdio>
#include <iostream>
#include <string>
#include <type_traits>
#include <sstream>
#include <functional>
namespace safe
{
    namespace detail
    {
        // No default.  Everything should either be a basic_string or a
        // scalar.  If something else gets by, this will fail to compile here
        template<typename T, typename Enable = void>
        struct remove_string;

        // Handles scalars (primatives + pointers) by simply returning the input.
        // will be optimized out.
        template<typename T>
        struct remove_string<T, typename std::enable_if<std::is_scalar<T>::value>::type>
        {
            static T convert(T val)
            {
                return val;
            }
        };

        // Handles basic strings by returning their c_str()
        template<typename Char, typename Traits>
        struct remove_string<std::basic_string<Char, Traits>, void>
        {
            typedef std::basic_string<Char, Traits> string_type;
            static auto convert(const string_type& val) -> decltype(val.c_str())
            {
                return val.c_str();
            }
        };

        // Default, resort to stringstream to return a string for the value
        template<typename T, typename Enable = void>
        struct remove_object
        {
            static std::string convert(const T& val)
            {
                std::ostringstream s;
                s << val;
                return s.str();
            }
        };

        // For primatives + pointers, just forward.  Note, the decay used earlier
        // causes arrays to decay to pointers and use this.
        template<typename T>
        struct remove_object<T, typename std::enable_if<std::is_scalar<T>::value>::type>
        {
            static T convert(T val)
            {
                return val;
            }
        };

        // For std::basic_string, just simply forward the reference, not copy is needed
        template<typename Char, typename Traits>
        struct remove_object<std::basic_string<Char, Traits>, void>
        {
            typedef std::basic_string<Char, Traits> string_type;
            static const string_type& convert(const string_type& val)
            {
                return val;
            }
        };

        // For reference wrappers, unwrap the reference, and then decay it and return
        // whatever that would return.
        template<typename T>
        struct remove_object<std::reference_wrapper<T>, void >
        {
            typedef remove_object< typename std::decay<T>::type > sub_call;
            static auto convert(std::reference_wrapper<T> const& ref) -> decltype(sub_call::convert(ref.get()))
            {
                return sub_call::convert(ref.get());
            }
        };

        template<typename ... Args>
        int printf(const char* fmt, const Args&... args)
        {
            //std::cout << __PRETTY_FUNCTION__ << std::endl;
            return std::printf(fmt, remove_string<typename std::decay<const Args&>::type >::convert(args)...);
        }

    }

    template<typename ... Args>
    int printf(const char* fmt, const Args&... args)
    {
        //std::cout << __PRETTY_FUNCTION__ << std::endl;
        return detail::printf(fmt, detail::remove_object<typename std::decay<const Args&>::type>::convert(args)...);
    }

    template<typename ... Args>
    int printf(const std::string& fmt, const Args&... args)
    {
        return printf(fmt.c_str(), args...);
    }

}

struct X
{
   int i;
   explicit X(int i) :
   i(i)
   {
   }
};

std::ostream& operator <<(std::ostream& str, const X& x)
{
  str << "X(" << x.i << ")";
  return str;
  
}
  

 int main()
 {
    const std::string h("Hello");
    char w[] = "World";
    const char* t = "There!";
    char* w2 = w;
    int i = 10;
    X x(456);
    using safe::printf;

    printf("%s %s %s %s %d %s, %s %f\n", h, w, t, std::string("Temp"), i, x, "foo", 0.12345);

    printf(std::string("%s %s %s\n"), "More fun!", x, w2);

    printf("Ref Test: %s %s %s\n", std::ref(w), std::ref(h), std::cref(x));    
}