如何创建可变参数模板字符串格式化程序 [英] How to create a variadic template string formatter
问题描述
我们需要一直格式化字符串。可以这么说:
We need to format strings all the time. It would be so nice to be able to say:
std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat
是否有C ++方式?我考虑过的一些替代方法:
Is there a C++ way of doing this? Some alternatives I considered:
-
snprintf
:使用原始的char
缓冲区。在现代C ++代码中不是很好。 -
std :: stringstream
:不支持格式模式字符串,相反,您必须推送笨拙的iomanip对象 -
boost :: format
:使用%的临时运算符重载
指定参数。丑陋的。
snprintf
: uses rawchar
buffers. Not nice in modern C++ code.std::stringstream
: does not support format pattern strings, instead you must push clumsy iomanip objects into the stream.boost::format
: uses an ad-hoc operator overload of%
to specify the arguments. Ugly.
可变参数模板现在没有更好的方法了,因为我们有了C ++ 11?
Isn't there a better way with variadic templates now that we have C++11?
推荐答案
它肯定可以用C ++ 11和可变参数模板编写。最好包装已经存在的东西,而不是尝试自己编写整个东西。如果您已经在使用Boost,则包装 boost :: format
这样非常简单:
It can certainly be written in C++11 with variadic templates. It's best to wrap something that already exists than to try to write the whole thing yourself. If you are already using Boost, it's quite simple to wrap boost::format
like this:
#include <boost/format.hpp>
#include <string>
namespace details
{
boost::format& formatImpl(boost::format& f)
{
return f;
}
template <typename Head, typename... Tail>
boost::format& formatImpl(
boost::format& f,
Head const& head,
Tail&&... tail)
{
return formatImpl(f % head, std::forward<Tail>(tail)...);
}
}
template <typename... Args>
std::string format(
std::string formatString,
Args&&... args)
{
boost::format f(std::move(formatString));
return details::formatImpl(f, std::forward<Args>(args)...).str();
}
您可以按照自己的方式使用:
You can use this the way you wanted:
std::string formattedStr = format("%s_%06d.dat", "myfile", 18); // myfile_000018.dat
如果您不想使用Boost(但您确实应该),那么您还可以包装 snprintf
。由于我们需要管理char缓冲区和旧式的非类型安全的可变长度参数列表,因此它涉及更多并且容易出错。通过使用 unique_ptr
可以使它更干净:
If you don't want to use Boost (but you really should) then you can also wrap snprintf
. It is a bit more involved and error-prone, since we need to manage char buffers and the old style non-type-safe variable length argument list. It gets a bit cleaner by using unique_ptr
's:
#include <cstdio> // snprintf
#include <string>
#include <stdexcept> // runtime_error
#include <memory> // unique_ptr
namespace details
{
template <typename... Args>
std::unique_ptr<char[]> formatImplS(
size_t bufSizeGuess,
char const* formatCStr,
Args&&... args)
{
std::unique_ptr<char[]> buf(new char[bufSizeGuess]);
size_t expandedStrLen = std::snprintf(buf.get(), bufSizeGuess, formatCStr, args...);
if (expandedStrLen >= 0 && expandedStrLen < bufSizeGuess)
{
return buf;
} else if (expandedStrLen >= 0
&& expandedStrLen < std::numeric_limits<size_t>::max())
{
// buffer was too small, redo with the correct size
return formatImplS(expandedStrLen+1, formatCStr, std::forward<Args>(args)...);
} else {
throw std::runtime_error("snprintf failed with return value: "+std::to_string(expandedStrLen));
}
}
char const* ifStringThenConvertToCharBuf(std::string const& cpp)
{
return cpp.c_str();
}
template <typename T>
T ifStringThenConvertToCharBuf(T const& t)
{
return t;
}
}
template <typename... Args>
std::string formatS(std::string const& formatString, Args&&... args)
{
// unique_ptr<char[]> calls delete[] on destruction
std::unique_ptr<char[]> chars = details::formatImplS(4096, formatString.c_str(),
details::ifStringThenConvertToCharBuf(args)...);
// string constructor copies the data
return std::string(chars.get());
}
snprintf $ c有一些区别$ c>和
boost :: format
就格式规范而言,但您的示例可同时使用。
There are some differences between snprintf
and boost::format
in terms of format specification but your example works with both.
这篇关于如何创建可变参数模板字符串格式化程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!