#ifndef TEST_SUPPORT_FORMAT_STRING_HPP #define TEST_SUPPORT_FORMAT_STRING_HPP #include <cstdio> #include <string> #include <memory> #include <array> #include <cstdarg> namespace format_string_detail { inline std::string format_string_imp(const char* msg, ...) { // we might need a second shot at this, so pre-emptivly make a copy struct GuardVAList { va_list& xtarget; bool active; GuardVAList(va_list& val) : xtarget(val), active(true) {} void clear() { if (active) va_end(xtarget); active = false; } ~GuardVAList() { if (active) va_end(xtarget); } }; va_list args; va_start(args, msg); GuardVAList args_guard(args); va_list args_cp; va_copy(args_cp, args); GuardVAList args_copy_guard(args_cp); std::array<char, 256> local_buff; std::size_t size = local_buff.size(); auto ret = ::vsnprintf(local_buff.data(), size, msg, args_cp); args_copy_guard.clear(); // handle empty expansion if (ret == 0) return std::string{}; if (static_cast<std::size_t>(ret) < size) return std::string(local_buff.data()); // we did not provide a long enough buffer on our first attempt. // add 1 to size to account for null-byte in size cast to prevent overflow size = static_cast<std::size_t>(ret) + 1; auto buff_ptr = std::unique_ptr<char[]>(new char[size]); ret = ::vsnprintf(buff_ptr.get(), size, msg, args); return std::string(buff_ptr.get()); } const char* unwrap(std::string& s) { return s.c_str(); } template <class Arg> Arg const& unwrap(Arg& a) { static_assert(!std::is_class<Arg>::value, "cannot pass class here"); return a; } } // namespace format_string_detail template <class... Args> std::string format_string(const char* fmt, Args const&... args) { return format_string_detail::format_string_imp( fmt, format_string_detail::unwrap(const_cast<Args&>(args))...); } #endif // TEST_SUPPORT_FORMAT_STRING_HPP