c++ 如何构造一个带有嵌入值的std::string,即“字符串插值”?

8tntrjer  于 2022-11-27  发布在  其他
关注(0)|答案(7)|浏览(187)

我想创建一个带有嵌入信息的字符串,一种(不是唯一的)实现方法叫做string interpolation或变量替换,即用实际值替换字符串中的占位符。
在C语言中,我会这样做:

printf("error! value was %d but I expected %d",actualValue,expectedValue)

而如果我用python编程,我会这样做:

"error! value was {0} but I expected {1}".format(actualValue,expectedValue)

这两个都是串内插的示例。
如何在C++中做到这一点?

重要注意事项

1.我知道,如果我想将这样的消息打印到标准输出(不是字符串插值,而是打印出我想要的字符串类型),我可以使用std::cout

cout << "error! value was " << actualValue << " but I expected "
<< expectedValue;

我不想 print 一个字符串到stdout。我想把一个std::string作为一个参数传递给一个函数(例如一个异常对象的构造函数)。
1.我使用的是C11,但是可移植性是一个潜在的问题,所以知道哪些方法在哪些C版本中有效,哪些方法在哪些C++版本中无效是一个优点。

编辑

1.对于我的直接使用,我并不关心性能(我正在为cryin' out loud引发一个异常!)。* 然而 *,了解各种方法的相对性能通常会非常非常有用。
1.为什么不直接使用printf本身(C++毕竟是C的超集...)?This answer讨论了一些不这样做的原因,就我所能理解的,类型安全是一个很大的原因:如果你输入%d,你输入的变量最好能转换成整数,因为这是函数判断它是什么类型的方法。使用一个编译时知道要插入的变量的实际类型的方法会安全得多。

aiazj4mn

aiazj4mn1#

在C++20中,您将能够使用std::format
这将支持python样式格式:

string s = std::format("{1} to {0}", "a", "b");

已经有可用的实作:是的。

zqdjd7g9

zqdjd7g92#

方法1:使用字符串流

看起来std::stringstream给出了一个快速解决方案:

std::stringstream ss;
ss << "error! value was " << actualValue << " but I expected " <<  expectedValue << endl;

//example usage
throw MyException(ss.str())
  • 肯定 *
  • 无外部依赖项
  • 我相信这在C++ 03和C++ 11中都是有效的。
  • 否定 *
  • 据说相当慢
  • 有点乱:您必须创建一个流,向其中写入数据,然后从中获取字符串。
    方法2:增强格式

Boost Format库也是一种可能性。使用它,您可以执行以下操作:

throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);
  • 肯定 *
  • 与stringstream方法相比非常干净:一个紧凑结构
  • 否定 *
  • 据说相当慢:在内部使用stream方法
  • 它是一种外部依赖
    编辑:
    方法3:变量模板参数

printf的类型安全版本似乎可以通过使用可变模板参数(技术术语,指使用无限数量模板参数的模板)来创建。

  • This question提供了一个简洁的示例,并讨论了该示例的性能问题。
  • This answer,其实现也相当紧凑,但是据报道仍然遭受性能问题。
  • this answer中讨论的The fmt library据说非常快,而且看起来像printf本身一样干净,但它是一个外部依赖项
  • 肯定 *
  • 用法是干净的:只需调用一个类似printf函数
  • 据报道,fmt库的速度相当快。
  • 其他选项看起来非常紧凑(不需要外部依赖)
  • 否定 *
  • fmt库虽然速度快,但它是一个外部依赖项,
  • 其他选项显然存在一些性能问题
hm2xizp9

hm2xizp93#

在C++11中,可以使用std::to_string

"error! value was " + std::to_string(actualValue) + " but I expected " + std::to_string(expectedValue)

它并不漂亮,但是很简单,你可以使用宏来缩小它。性能不是很好,因为你没有预先reserve()空间。Variadic templates可能会更快,看起来更好。
这种字符串构造(而不是插值)也不利于本地化,但如果需要的话,可能会使用库。

goqiplq2

goqiplq24#

使用您喜欢的任何内容:
1)std::字符串流

#include <sstream>
std::stringstream ss;
ss << "Hello world!" << std::endl;
throw std::runtime_error(ss.str());

2)库文件格式:https://github.com/fmtlib/fmt

#include <stdexcept>
throw std::runtime_error(
    fmt::format("Error has been detected with code {} while {}",
        0x42, "copying"));
edqdpe6u

edqdpe6u5#

C++17解决方案可同时用于std::stringstd::wstring(在VS2019和VS2022上测试):

#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>

template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
    int size_signed{ 0 };

    // 1) Determine size with error handling:    
    if constexpr (std::is_same_v<T, char>) { // C++17
        size_signed = std::snprintf(nullptr, 0, format, args ...);
    }
    else {
        size_signed = std::swprintf(nullptr, 0, format, args ...);
    }  
    if (size_signed <= 0) {
        throw std::runtime_error("error during formatting.");
    }
    const auto size = static_cast<size_t>(size_signed);

    // 2) Prepare formatted string:
    std::basic_string<T> formatted(size, T{});
    if constexpr (std::is_same_v<T, char>) { // C++17
        std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }
    else {
        std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }

    return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy. 
}

// USE EXAMPLE: //

int main()
{
    int i{ 0 }; 
    const std::string example1 = string_format("string. number %d.", ++i); // => "string. number 1."  
    const std::wstring example2 = string_format(L"wstring. number %d.", ++i); // => L"wstring. number 2."
}
balp4ylt

balp4ylt6#

免责声明:
后面的代码是基于我2年前读到的一篇文章,我会尽快找到源代码并放在这里。
这是我在我的C17项目中使用的。应该与任何支持可变模板的C编译器一起工作。
用法:

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 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;
}
//<-----------------------------------------------------------------------------
whlutmcx

whlutmcx7#

如果您不介意使用预处理器脚本,下面是一个更简单但方便的解决方案:https://github.com/crazybie/cpp_str_interpolation。然后您可以编写如下代码:

string s1 = "world", s2 = "!";
cout << _F("hello, {s1+s2}") << endl;

它还支持使用类似模板引擎:

int a = 1;
float b = 2.3f;
cout << _F(R"(
`for (int i=0; i<2; i++) {`
    a is {a}, i is {i}.
    a+i is {a+i}.
`}`
b is {b}.
cout << "123" << endl;`
)") << endl;

相关问题