【问题标题】:How to construct a std::string with embedded values, i.e. "string interpolation"?如何构造带有嵌入值的 std::string,即“字符串插值”?
【发布时间】:2016-10-23 16:34:27
【问题描述】:

我想创建一个带有嵌入信息的字符串。实现我想要的一种方法(不是唯一方法)称为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;

我不想打印一个字符串到标准输出。我想将std::string 作为参数传递给函数(例如异常对象的构造函数)。

  1. 我使用的是 C++11,但可移植性可能是一个问题,因此知道哪些方法在 C++ 的哪些版本中有效和无效将是一个加分项。

编辑

  1. 对于我的直接使用,我不关心性能(我提出了一个例外,因为我要大声哭泣!)。 然而,了解各种方法的相对性能通常会非常有用。

  2. 为什么不直接使用 printf 本身(毕竟 C++ 是 C 的超集...)? This answer 讨论了一些为什么不这样做的原因。据我所知,类型安全是一个很大的原因:如果你放 %d,那么你放在那里的变量最好真的可以转换为整数,因为这就是函数确定它是什么类型的方式。如果有一个方法使用编译时知道要插入的变量的实际类型的方法会更安全。

【问题讨论】:

  • c++ 仍然没有这样做的标准方法吗?我很惊讶,它设法获得线程但不是现代 printf
  • c++ 中使用printffprintf(std::cout, ... 怎么样?
  • Stroustrupp 将可变参数模板函数与类型安全的 printf 结合使用的一个很好的例子。我实际上认为这是一个很好的实现。
  • 顺便说一句,这叫做字符串插值。
  • ...%dint,而不是 double

标签: c++ string-interpolation


【解决方案1】:

在 C++20 中,您将能够使用 std::format

这将支持 python 样式格式:

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

已经有一个可用的实现:https://github.com/fmtlib/fmt

【讨论】:

  • 另请参阅 cppreference.com 上的 std::format()。请注意,在格式占位符中指定索引是可选的:string s1 = std::format("{} to {}", "a", "b");
【解决方案2】:

方法一:使用字符串流

看起来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。

否定

  • 据说很慢
  • 有点乱:你必须创建一个流,写入它,然后从中取出字符串。

方法二:提升格式

Boost Format 库也是一种可能。使用这个,你会这样做:

throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);

正面

  • 与 stringstream 方法相比相当干净:一个紧凑的结构

否定

  • 据说很慢:内部使用流方法
  • 这是一个外部依赖项

编辑:

方法三:可变参数模板参数

似乎可以通过使用可变参数模板参数(模板的技术术语,它采用不定数量的模板参数)来创建 printf 的类型安全版本。我在这方面看到了许多可能性:

  • This question 给出了一个简洁的示例并讨论了该示例的性能问题。
  • This answer 这个问题,其实现也相当紧凑,但据报道仍然存在性能问题。
  • The fmt library,在 this answer 中讨论过,据说速度非常快,而且看起来和 printf 本身一样干净,但它是一个外部依赖项

正面

  • 用法很简洁:只需调用类似 printf 的函数
  • 据说 fmt 库非常快
  • 其他选项看起来相当紧凑(不需要外部依赖)

否定

  • fmt 库虽然速度很快,但却是一个外部依赖项
  • 其他选项显然存在一些性能问题

【讨论】:

  • 值得注意的是,第一个解决方案以后将很难本地化。有意义的信息会溢出到无意义的部分,某些语言可能需要不同的参数顺序来形成声音信息
  • 这是一个很好的观点,尽管有人可能会争辩说第二种方法确实不容易搜索。在查看这样的错误消息时,我倾向于搜索看起来像常量部分的内容(即我会搜索“错误!值是”或“但我期望”)。理想情况下,错误消息本身应该有一些可以搜索的独特的前导内容(例如“错误#5:”),但这是错误的结构问题......
  • @stochastic “某些语言”是真实语言,而不是编程语言。
  • @stochastic,“未定义的 sin 引用”提供了比“未定义的引用”更好的搜索结果,所以这取决于。我所说的语言是指人类语言。有些语言可能需要不同的参数顺序才能听起来不像 Yoda。有些可能在最后一个参数之后需要额外的标点符号。
  • 是的,最好将整个字符串本地化。使用boost::format() 的替代方法是简单地用std::string::replace() 填充占位符,例如:std::string s = "error! value was %1% but I expected %2%"; std::string::size_type idx = s.find("%1%"); s.replace(idx, 3, std::to_string(acutalValue)); idx = s.find("%2%"); s.replace(idx, 3,std::to_string(expectedValue)); throw MyException(s); 不如boost::format() 甚至std::stringstream 优雅,但在简单的情况下仍然可用。
【解决方案3】:

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

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

它并不漂亮,但它很简单,您可以使用宏将其缩小一点。性能不是很好,因为您事先没有reserve() 空间。 Variadic templates 可能会更快,看起来更好。

这种字符串构造(而不是插值)也不利于本地化,但如果需要,您可能会使用库。

【讨论】:

    【解决方案4】:

    随意使用:

    1) 标准::字符串流

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

    2) libfmt : https://github.com/fmtlib/fmt

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

    【讨论】:

      【解决方案5】:

      免责声明:
      后面的代码是基于我2年前读过的一篇文章。我会尽快找到来源并放在这里。

      这是我在 C++17 项目中使用的。应该可以与任何支持可变参数模板的 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;
      }
      //<-----------------------------------------------------------------------------
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-06
        • 1970-01-01
        • 1970-01-01
        • 2015-07-11
        • 2021-04-23
        • 2017-01-26
        • 2012-03-25
        • 1970-01-01
        相关资源
        最近更新 更多