C++ 格式宏/内联 ostringstream

发布于 2024-07-09 00:20:54 字数 1408 浏览 6 评论 0原文

我正在尝试编写一个宏,允许我执行以下操作: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 *),它将以十六进制打印该字符串的地址,接下来的项目将正常打印。 如果第一项是 std::string,它将无法编译(没有匹配的运算符 <<)。

这:

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

给出了看起来正确的输出,但是 0\b 当然存在于字符串中。

以下似乎可以工作,但编译时会出现警告(获取临时地址):

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

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

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

I'm trying to write a macro that would allow me to do something like: FORMAT(a << "b" << c << d), and the result would be a string -- the same as creating an ostringstream, inserting a...d, and returning .str(). Something like:

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

Essentially, FORMAT(a << "b" << c << d) == f().

First, I tried:

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

If the very first item is a C string (const char *), it will print the address of the string in hex, and the next items will print fine. If the very first item is an std::string, it will fail to compile (no matching operator <<).

This:

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

gives what seems like the right output, but the 0 and \b are present in the string of course.

The following seems to work, but compiles with warnings (taking address of temporary):

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

Does anyone know why 1 prints the address of the c-string and fails to compile with the std::string? Aren't 1 and 3 essentially the same?

I suspect that C++0x variadic templates will make format(a, "b", c, d) possible. But is there a way to solve this now?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

无尽的现实 2024-07-16 00:20:54

你们都已经很清楚这一点了。 但遵循起来有点困难。 因此,让我尝试总结一下您所说的内容...


这里的困难是:

  • 我们正在使用临时的 ostringstream 对象,因此获取地址是禁忌的。< /p>

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

    因为它是

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

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

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


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

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

分配: ostringstream()

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

  • ostringstream() << std::string() // 感谢 *David Norman*
  • ostringstream() << std::dec // 感谢 *cadabra*

或者我们可以使用:

我们不能使用:

  • < Strike>运算符<<( ostringstream(), "" )
  • (ostream &) ostringstream()

附加: 现在就简单了。

转换回来:我们可以只使用(ostringstream&)。 但dynamic_cast会更安全。 万一 dynamic_cast 返回 NULL(不应该),下面的 .str() 将触发 coredump。

调用str()猜猜。


把它们放在一起。

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

参考文献:

You've all pretty much nailed this already. But it's a little challenging to follow. So let me take a stab at summarizing what you've said...


That difficulties here are that:

  • We are playing with a temporary ostringstream object, so taking addresses is contra-indicated.

  • Because it's a temporary, we cannot trivially convert to an ostream object through casting.

  • Both the constructor [obviously] and str() are class ostringstream methods.
    (Yes, we need to use .str(). Using the ostringstream object directly would wind up invoking ios::operator void*(), returning a pointer-like good/bad value and not a string object.)

  • operator<<(...) exists as both inherited ostream methods and global functions. In all cases it returns an ostream& reference.

  • The choices here for ostringstream()<<"foo" are the inherited method ostream::operator<<(void* ) and the global function operator<<(ostream&,const char* ). The inherited ostream::operator<<(void* ) wins out because we can't convert to an ostream object reference to invoke the global function. [Kudos to coppro!]


So, to pull this off, we need to:

  • Allocate a temporary ostringstream.
  • Convert it to an ostream.
  • Append data.
  • Convert it back to an ostringstream.
  • And invoke str().

Allocating: ostringstream().

Converting: There are several choices. Others have suggested:

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

Or we could use:

We cannot use:

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

Appending: Straightforward now.

Converting back: We could just use (ostringstream&). But a dynamic_cast would be safer. In the unlikely event dynamic_cast returned NULL (it shouldn't), the following .str() will trigger a coredump.

Invoking str(): Guess.


Putting it all together.

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

References:

.

孤独患者 2024-07-16 00:20:54

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

更新:由于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;

Here is what I use. It all fits into one tidy class definition in a header file.

update: major improvement to the code thanks to 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; }
};

Here is how it is used:

string myString = MakeString() << a << "b" << c << d;
明月松间行 2024-07-16 00:20:54

您遇到的问题与 operator << (ostream&, char*) 不是 ostream 的成员,并且您的临时 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 预处理器文档,它对于此类事情具有非常强大的功能。 (在调用 BOOST_PP_REPEAT 调用生成函数后,您可以随后#undef宏)

The problem you are having is related to the fact that operator << (ostream&, char*) is not a member of ostream, and your temporary ostream instance cannot bind to a non-const reference. Instead, it picks the void* overload, which is a member of ostream, and thus doesn't have that restriction.

The best (but not easiest or most elegant, by any stretch of imagination!) would be to use the Boost Preprocessor to generate a large number of function overloads, each templated on a large number of objects (includes have been omitted and assuming 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(); \
    }

It's not guaranteed to work exactly (wrote it without testing), but that's basically the idea. You then call BOOST_PP_REPEAT(N, MAKE_FORMAT, ()) to create a series of functions taking up to N parameters that will format your string as you want to (replace N with the integer of choice. Higher values may negatively affect compile times). This should suffice until you get a compiler with variadic templates. You should read the boost preprocessor documentation, it has very powerful features for things like this. (you can subsequently #undef the macros, after calling the BOOST_PP_REPEAT invocation to generate the functions)

如此安好 2024-07-16 00:20:54

这是一个像 cadabra 一样的答案,不会干扰 ostream 状态:

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

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

Here's an answer like cadabra's that doesn't mess with the ostream state:

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

I believe the first paragraph of coppro's answer describes why things behave this way.

自我难过 2024-07-16 00:20:54

这是一个可行的解决方案:

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

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

Here's a working solution:

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

I don't quite understand the behavior of the first argument.

↘紸啶 2024-07-16 00:20:54

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

几个小时后(我花了很多时间挠头并在这里问一个“重新加载”问题),我发现seekp()调用是罪魁祸首。 我不确定 MSVC++ 的做法有何不同,但

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

用cadabra 的

ostringstream() << std::dec

MSVC++ 的作品替换也是如此。

When I took mrree's solution (the one marked "preferred", the one beautifully explained, and the one working perfectly for G++), I ran into problems with MSVC++: All strings built with this macro ended up empty.

Hours (and lots of scratching my head and asking a "reloaded" question here) later, I found out that the seekp() call was the culprit. I am not sure what MSVC++ does differently with that, but replacing

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

with cadabra's

ostringstream() << std::dec

works for MSVC++, too.

人│生佛魔见 2024-07-16 00:20:54

为什么不直接使用函数而不是宏呢?

Why not just use a function instead of a macro?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文