15

我正在尝试编写一个允许我执行类似操作的宏:FORMAT(a << "b" << c << d),结果将是一个字符串 - 与创建 ostringstream、插入a...d和返回相同.str()。就像是:

string f(){
   ostringstream o;
   o << a << "b" << c << d;
   return o.str()
}

本质上,FORMAT(a << "b" << c << d) == f().

首先,我试过:

1: #define FORMAT(items)                                                   \
   ((std::ostringstream&)(std::ostringstream() << items)).str()

如果第一项是 C 字符串 ( const char *),它将以十六进制打印字符串的地址,并且下一项将正常打印。如果第一项是 an std::string,它将无法编译(没有匹配的 operator <<)。

这:

2: #define FORMAT(items)                                                   \
   ((std::ostringstream&)(std::ostringstream() << 0 << '\b' << items)).str()

给出看似正确的输出,但0and\b当然存在于字符串中。

以下似乎有效,但编译时带有警告(获取临时地址):

3: #define FORMAT(items)                                                   \
   ((std::ostringstream&)(*((std::ostream*)(&std::ostringstream())) << items)).str()

有谁知道为什么 1 打印 c-string 的地址并且无法使用std::string? 1和3本质上不是一样的吗?

我怀疑 C++0x 可变参数模板将成为format(a, "b", c, d)可能。但是现在有办法解决这个问题吗?

4

7 回答 7

21

这是我使用的。这一切都适合头文件中的一个整洁的类定义。

更新:感谢litb对代码的重大改进。

// makestring.h:

class MakeString
{
    public:
        std::stringstream stream;
        operator std::string() const { return stream.str(); }

        template<class T>
        MakeString& operator<<(T const& VAR) { stream << VAR; return *this; }
};

以下是它的使用方法:

string myString = MakeString() << a << "b" << c << d;
于 2008-11-19T22:58:14.343 回答
21

你们几乎都已经搞定了。但这有点具有挑战性。所以让我试着总结一下你所说的......


这里的困难在于:

  • 我们正在玩一个临时ostringstream对象,所以取地址是禁忌的。

  • 因为它是临时的,所以我们ostream不能通过强制转换简单地转换为对象。

  • 构造函数 [显然] 和str()都是类ostringstream方法。(是的,我们需要使用.str()ostringstream直接使用对象最终会调用ios::operator void*(),返回类似指针的好/坏值,而不是字符串对象。)

  • operator<<(...)作为继承ostream方法和全局函数存在。在所有情况下,它都会返回一个ostream&引用。

  • 这里的选择ostringstream()<<"foo"是继承的方法ostream::operator<<(void* )和全局函数operator<<(ostream&,const char* )。继承ostream::operator<<(void* )胜出,因为我们无法转换为ostream对象引用来调用全局函数。[向coppro致敬!]


因此,要实现这一目标,我们需要:

  • 分配一个临时的ostringstream.
  • 将其转换为ostream.
  • 追加数据。
  • 将其转换回ostringstream.
  • 并调用str().

分配: ostringstream()

转换: 有多种选择。其他人建议:

  • ostringstream() << std::string() // Kudos to *David Norman*
  • ostringstream() << std::dec // Kudos to *cadabra*

或者我们可以使用:

我们不能使用:

  • operator<<( ostringstream(), "" )
  • (ostream &) ostringstream()

附加: 现在直截了当。

转换回来: 我们可以只使用(ostringstream&). 但是adynamic_cast会更安全。在不太可能发生的(不应该),以下dynamic_cast将触发核心转储。NULL.str()

调用str() 猜。


把它们放在一起。

#define FORMAT(ITEMS)                                             \
  ( ( dynamic_cast<ostringstream &> (                             \
         ostringstream() . seekp( 0, ios_base::cur ) << ITEMS )   \
    ) . str() )

参考:

.

于 2008-11-20T17:03:09.757 回答
4

您遇到的问题与不是 ostream 成员的事实有关operator << (ostream&, char*),并且您的临时 ostream 实例无法绑定到非const引用。相反,它选择作为void*ostream 成员的重载,因此没有该限制。

最好的(但不是最简单或最优雅的,无论如何想象!)是使用 Boost 预处理器生成大量函数重载,每个函数都以大量对象为模板(包括已被省略并假设using namespace std;):

#define MAKE_OUTPUT(z, n, data) \
    BOOST_PP_TUPLE_ELEM(2, 0, data) << BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(2, 1, data), n);

#define MAKE_FORMAT(z, n, data) \
    template <BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), typename T)> \
    inline string format(BOOST_PP_ENUM_BINARY_PARAMS_Z(z, BOOST_PP_INC(n), T, p)) \
    { \
      ostringstream s; \
      BOOST_PP_REPEAT_##z(z, n, MAKE_OUTPUT, (s, p)); \
      return s.str(); \
    }

它不能保证完全工作(没有测试就写出来),但这基本上就是这个想法。然后,您调用BOOST_PP_REPEAT(N, MAKE_FORMAT, ())创建一系列函数,最多占用 N 个参数,这些参数将根据需要格式化您的字符串(将 N 替换为选择的整数。较高的值可能会对编译时间产生负面影响)。在您获得带有可变参数模板的编译器之前,这应该足够了。您应该阅读 boost 预处理器文档,它具有非常强大的功能来处理此类事情。(在调用调用生成函数之后,您可以随后#undef使用宏)BOOST_PP_REPEAT

于 2008-11-19T22:26:22.147 回答
2

这是一个像 cadabra 这样的答案,它不会与 ostream 状态混淆:

#define FORMAT(items)     static_cast<std::ostringstream &>((std::ostringstream() << std::string() << items)).str()

我相信 coppro 答案的第一段描述了为什么事情会这样。

于 2008-11-19T22:41:01.087 回答
1

这是一个有效的解决方案:

#define FORMAT(items)                                                   \
   ((std::ostringstream&)(std::ostringstream() << std::dec << items)).str()

我不太了解第一个参数的行为。

于 2008-11-19T22:30:31.950 回答
1

当我采用 mrree 的解决方案(标记为“首选”的解决方案、解释精美的解决方案以及适用于 G++ 的解决方案)时,我遇到了 MSVC++ 的问题:使用此宏构建的所有字符串最终都是空的。

几个小时(还有很多挠头并在这里问一个“重新加载”的问题)之后,我发现 seekp() 调用是罪魁祸首。我不确定 MSVC++ 有什么不同,但是替换

ostringstream().seekp( 0, ios_base::cur )

与卡达布拉的

ostringstream() << std::dec

也适用于 MSVC++。

于 2009-01-30T10:35:06.167 回答
0

为什么不只使用函数而不是宏?

于 2008-11-19T22:31:36.050 回答