C++ 中的“字符串插值”:构造一个带有嵌入值的 std::string(例如用于错误消息)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/37956090/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
"string interpolation" in C++: Construct a std::string with embedded values (e.g. for error messages)?
提问by stochastic
I want to create a string with embedded information. One way (not the only way) of achieving what I want is called string interpolationor variable substitution, wherein placeholders in a string are replaced with actual values.
我想创建一个带有嵌入信息的字符串。实现我想要的一种方法(不是唯一方法)称为字符串插值或变量替换,其中字符串中的占位符被替换为实际值。
In C, I would do something like this:
在 C 中,我会做这样的事情:
printf("error! value was %d but I expected %d",actualValue,expectedValue)
whereas if I were programming in python, I would do something like this:
而如果我用 python 编程,我会做这样的事情:
"error! value was {0} but I expected {1}".format(actualValue,expectedValue)
both of these are examples of string interpolation.
这两个都是字符串插值的例子。
How can I do this in C++?
我怎样才能在 C++ 中做到这一点?
Important Caveats:
重要警告:
- I know that I can use
std::cout
if I want to print such a message to standard output (not string interpolation, but prints out the kind of string I want):
- 我知道
std::cout
如果我想将这样的消息打印到标准输出(不是字符串插值,而是打印出我想要的字符串类型),我可以使用:
cout << "error! value was " << actualValue << " but I expected "
<< expectedValue;
I don't want to printa string to stdout. I want to pass a std::string
as an argument to a function (e.g. the constructor of an exception object).
我不想将字符串打印到标准输出。我想将 astd::string
作为参数传递给函数(例如异常对象的构造函数)。
- I am using C++11, but portability is potentially an issue, so knowing which methods work and don't work in which versions of C++ would be a plus.
- 我正在使用 C++11,但可移植性可能是一个问题,因此知道哪些方法在 C++ 的哪个版本中有效和无效将是一个加分项。
Edit
编辑
For my immediate usage, I'm not concerned about performance (I'm raising an exception for cryin' out loud!). However, knowing the relative performance of the various methods would be very very useful in general.
Why not just use printf itself (C++ is a superset of C after all...)? This answerdiscusses some reasons why not. As far as I can understand, type safety is a big reason: if you put %d, the variable you put in there had better really be convertible to an integer, as that's how the function figures out what type it is. It would be much safer to have a method which uses compile-time knowledge of the actual type of the variables to be inserted.
对于我的直接使用,我并不关心性能(我为大声哭泣提出了一个例外!)。 但是,了解各种方法的相对性能通常非常有用。
为什么不直接使用 printf 本身(毕竟 C++ 是 C 的超集......)? 这个答案讨论了一些原因。据我所知,类型安全是一个很大的原因:如果你输入 %d,你输入的变量最好真的可以转换为整数,因为这就是函数确定它是什么类型的方式。拥有一种使用要插入的变量的实际类型的编译时知识的方法会更安全。
回答by stochastic
Method 1: Using a string stream
方法一:使用字符串流
It looks like std::stringstream
gives a quick solution:
看起来std::stringstream
提供了一个快速的解决方案:
std::stringstream ss;
ss << "error! value was " << actualValue << " but I expected " << expectedValue << endl;
//example usage
throw MyException(ss.str())
Positive
积极的
- no external dependencies
- I believe this works in C++ 03 as well as c++ 11.
- 没有外部依赖
- 我相信这适用于 C++ 03 和 C++ 11。
Negative
消极的
- reportedly quite slow
- a bit more messy: you must create a stream, write to it, and then get the string out of it.
- 据说很慢
- 有点乱:你必须创建一个流,写入它,然后从中取出字符串。
Method 2: Boost Format
方法 2:Boost 格式
The Boost Formatlibrary is also a possibility. Using this, you would do:
该升压格式库也是一种可能性。使用这个,你会这样做:
throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);
Positive
积极的
- pretty clean compared to stringstream method: one compact construct
- 与 stringstream 方法相比非常干净:一种紧凑的结构
Negative
消极的
- reportedly quite slow: uses the stream method internally
- it's an external dependency
- 据说很慢:在内部使用流方法
- 这是一个外部依赖
Edit:
编辑:
Method 3: variadic template parameters
方法三:可变模板参数
It seems that a type-safe version of printf can be created by using variadic template parameters (the techincal term for a template that takes an indefinite number of template parameters). I have seen a number of possibilities in this vein:
似乎可以通过使用可变参数模板参数(采用无限数量模板参数的模板的技术术语)来创建类型安全的 printf 版本。我在这方面看到了许多可能性:
- This questiongives a compact example and discusses performance problems with that example.
- This answerto that question, whose implementation is also quite compact, but reportedly still suffers from performance issues.
- The fmt library, discussed in this answer, is reportedly quite fast and seems to be as clean as printf itself, but is an external dependency
- 这个问题给出了一个紧凑的例子并讨论了该例子的性能问题。
- 这个问题的答案,其实现也非常紧凑,但据报道仍然存在性能问题。
- 在这个答案中讨论的 fmt 库据说速度非常快,似乎和 printf 本身一样干净,但它是一个外部依赖项
Positive
积极的
- usage is clean: just call a printf-like function
- The fmt library is reportedly quite fast
- The other options seem quite compact (no external dependency required)
- 用法很干净:只需调用一个类似 printf 的函数
- 据报道,fmt 库相当快
- 其他选项看起来很紧凑(不需要外部依赖)
Negative
消极的
- the fmt library, while fast, is an external dependency
- the other options apparently have some performance issues
- fmt 库虽然速度快,但它是一个外部依赖项
- 其他选项显然有一些性能问题
回答by Stephan Dollberg
In C++20 you will be able to use std::format
.
在 C++20 中,您将能够使用std::format
.
See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.htmlfor the accepted paper.
接受的论文见http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html。
This will support python style formatting:
这将支持 python 样式格式:
string s1 = std::format("{1} to {0}", "a", "b");
There is already an implementation available: https://github.com/fmtlib/fmt
已经有一个可用的实现:https: //github.com/fmtlib/fmt
回答by Peter Tseng
In C++11 you can use std::to_string
:
在 C++11 中,您可以使用std::to_string
:
"error! value was " + std::to_string(actualValue) + " but I expected " + std::to_string(expectedValue)
It's not pretty, but it's straightforward, and you can use a macro to shrink it a bit. Performance is not great, since you do not reserve()
space beforehand. Variadic templateswould probably be faster and look nicer.
它并不漂亮,但很简单,您可以使用宏将其缩小一点。性能不是很好,因为你reserve()
事先没有空间。可变模板可能会更快,看起来更好。
This kind of string construction (instead of interpolation) is also bad for localization, but you'd probably use a library if you needed that.
这种字符串构造(而不是插值)也不利于本地化,但如果需要,您可能会使用库。
回答by Vyacheslav Napadovsky
Use whatever you like:
使用任何你喜欢的:
1) std::stringstream
1) std::stringstream
#include <sstream>
std::stringstream ss;
ss << "Hello world!" << std::endl;
throw std::runtime_error(ss.str());
2) libfmt : https://github.com/fmtlib/fmt
2)libfmt:https: //github.com/fmtlib/fmt
#include <stdexcept>
throw std::runtime_error(
fmt::format("Error has been detected with code {} while {}",
0x42, "copying"));
回答by MABVT
DISCLAIMER: The subsequent code is based on an article I read 2 years ago. I will find the source and put it here ASAP.
免责声明:后续代码基于我 2 年前阅读的一篇文章。我会尽快找到来源并把它放在这里。
This is what I use in my C++17 project. Should work with any C++ compiler supporting variadic templates though.
这是我在 C++17 项目中使用的。应该可以与任何支持可变参数模板的 C++ 编译器一起使用。
Usage:
用法:
std::string const word = "Beautiful";
std::string const message = CString::format("%0 is a %1 word with %2 characters.\n%0 %2 %0 %1 %2", word, "beautiful", word.size());
// Prints:
// Beautiful is a beautiful word with 9 characters.
// Beautiful 9 Beautiful beautiful 9.
The class implementation:
类实现:
/**
* The CString class provides helpers to convert 8 and 16-bit
* strings to each other or format a string with a variadic number
* of arguments.
*/
class CString
{
public:
/**
* Format a string based on 'aFormat' with a variadic number of arbitrarily typed arguments.
*
* @param aFormat
* @param aArguments
* @return
*/
template <typename... TArgs>
static std::string format(
std::string const&aFormat,
TArgs &&...aArguments);
/**
* Accept an arbitrarily typed argument and convert it to it's proper
* string representation.
*
* @tparam TArg
* @tparam TEnable
* @param aArg
* @return
*/
template <
typename TArg,
typename TEnable = void
>
static std::string toString(TArg const &aArg);
/**
* Accept a float argument and convert it to it's proper string representation.
*
* @tparam TArg
* @param arg
* @return
*/
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
static std::string toString(const float& arg);
/**
* Convert a string into an arbitrarily typed representation.
*
* @param aString
* @return
*/
template <
typename TData,
typename TEnable = void
>
static TData const fromString(std::string const &aString);
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
static TData fromString(std::string const &aString);
private:
/**
* Format a list of arguments. In this case zero arguments as the abort-condition
* of the recursive expansion of the parameter pack.
*
* @param aArguments
*/
template <std::size_t NArgs>
static void formatArguments(std::array<std::string, NArgs> const &aArguments);
/**
* Format a list of arguments of arbitrary type and expand recursively.
*
* @param outFormatted
* @param inArg
* @param inArgs
*/
template <
std::size_t NArgs,
typename TArg,
typename... TArgs
>
static void formatArguments(
std::array<std::string, NArgs> &aOutFormatted,
TArg &&aInArg,
TArgs &&...aInArgs);
};
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename... TArgs>
std::string CString::format(
const std::string &aFormat,
TArgs &&...aArgs)
{
std::array<std::string, sizeof...(aArgs)> formattedArguments{};
formatArguments(formattedArguments, std::forward<TArgs>(aArgs)...);
if constexpr (sizeof...(aArgs) == 0)
{
return aFormat;
}
else {
uint32_t number = 0;
bool readNumber = false;
std::ostringstream stream;
for(std::size_t k = 0; k < aFormat.size(); ++k)
{
switch(aFormat[k])
{
case '%':
readNumber = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Desired behaviour to enable reading numbers in text w/o preceding %
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
if(readNumber)
{
number *= 10;
number += static_cast<uint32_t>(aFormat[k] - '0');
break;
}
default:
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
stream << aFormat[k];
break;
#pragma GCC diagnostic warning "-Wimplicit-fallthrough"
}
}
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
return stream.str();
}
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename TArg, typename enable>
std::string CString::toString(TArg const &aArg)
{
std::ostringstream stream;
stream << aArg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
std::string CString::toString(const float& arg) {
std::ostringstream stream;
stream << std::setprecision(12) << arg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount>
void CString::formatArguments(std::array<std::string, argCount> const&aArgs)
{
// Unused: aArgs
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount, typename TArg, typename... TArgs>
void CString::formatArguments(
std::array<std::string, argCount> &outFormatted,
TArg &&inArg,
TArgs &&...inArgs)
{
// Executed for each, recursively until there's no param left.
uint32_t const index = (argCount - 1 - sizeof...(TArgs));
outFormatted[index] = toString(inArg);
formatArguments(outFormatted, std::forward<TArgs>(inArgs)...);
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
TData CString::fromString(std::string const &aString)
{
TData const result{};
std::stringstream ss(aString);
ss >> result;
return result;
}
//<-----------------------------------------------------------------------------
}
}