#ifndef TEST_SUPPORT_FORMAT_STRING_H #define TEST_SUPPORT_FORMAT_STRING_H #include #include #include #include #include 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 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(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(ret) + 1; auto buff_ptr = std::unique_ptr(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 Arg const& unwrap(Arg& a) { static_assert(!std::is_class::value, "cannot pass class here"); return a; } } // namespace format_string_detail template 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))...); } #endif // TEST_SUPPORT_FORMAT_STRING_HPP