【问题标题】:How to overload std::cout << std::endl?如何重载 std::cout << std::endl?
【发布时间】:2020-03-19 14:46:33
【问题描述】:

我想知道是否无论如何我可以为endl 重载std::cout &lt;&lt; std::endl; 以不仅创建一个换行符,而且还打印一个'-' 应该是换行符的位置,然后打印另一个换行符。

就像我做了std::cout &lt;&lt; std::endl &lt;&lt; '-' &lt;&lt; std::endl;

所以我假设我必须重载&lt;&lt;,但我不确定从那里去哪里才能与endl 一起工作。

【问题讨论】:

  • 从您在下面给出的答案中发布的 cmets 中,听起来您的情况相当受限,其中 (1) 您有无法修改的代码,以及 (2) 您需要更改它输出什么。你能详细说明这里的上下文吗?您是否有不能修改现有代码的原因?您是否有理由考虑改变std::endl 的工作方式?不涉及更改 std::endl 的解决方案是否适合您?
  • 是的,他们的理由是我不允许删除主文件中的任何内容,但主文件有这个功能 std::cout
  • @DiegoEsquivel 请编辑您的问题(下面有一个“编辑”链接)并将所有要求详细添加到问题中。您已经获得了包含某些要求或没有要求的各种答案。还要在问题中添加main,因为如果有解决方案根本,它将非常具体到main 的内容。

标签: c++ overloading std endl


【解决方案1】:

epic question about indenting std::ostream instances 的启发,这是一个将添加额外字符的codecvt 类。

该类改编自 @MartinYork 的 popular answer:我复制粘贴了该类,对其进行了调整以使用不同的字符,并将 for 循环重写为我认为更自然的形式。

这是working example

#include <iostream>
#include <locale>

class augmented_newline_facet : public std::codecvt<char, char, std::mbstate_t>
{
    const char addition = '-';
public:
    explicit augmented_newline_facet(const char addition, size_t refs = 0) : std::codecvt<char,char,std::mbstate_t>(refs), addition{addition} {}

    using result = std::codecvt_base::result;
    using base = std::codecvt<char,char,std::mbstate_t>;
    using intern_type = base::intern_type;
    using extern_type = base::extern_type;
    using state_type = base::state_type;

    int& state(state_type& s) const {return *reinterpret_cast<int*>(&s);}
  protected:
    virtual result do_out(state_type& addition_needed,
                          const intern_type* rStart, const intern_type* rEnd, const intern_type*&   rNewStart,
                          extern_type* wStart, extern_type* wEnd, extern_type*& wNewStart) const override
    {
        result  res = std::codecvt_base::noconv;

        while ((rStart < rEnd) && (wStart < wEnd))
        {
            // The last character seen was a newline.
            // Thus we need to add the additional character and an extra newline.
            if (state(addition_needed) == 1)
            {
                *wStart++ = addition;
                *wStart++ = '\n';
                state(addition_needed) = 0;
                res = std::codecvt_base::ok;
                continue;
            }
            else
            {
                // Copy the next character.
                *wStart = *rStart;
            }

            // If the character copied was a '\n' mark that state
            if (*rStart == '\n')
            {
                state(addition_needed) = 1;
            }

            ++rStart;
            ++wStart;
        }

        if (rStart != rEnd)
        {
            res = std::codecvt_base::partial;
        }
        rNewStart   = rStart;
        wNewStart   = wStart;

        return res;
    }

    virtual bool do_always_noconv() const throw() override
    {
        return false; 
    }

};

int main(int argc, char* argv[]) {
    std::ios::sync_with_stdio(false);
    std::cout.imbue(std::locale(std::locale::classic(), 
                                new augmented_newline_facet{'-'}));

    for (int i = 0; i < 5; ++i)
    {
        std::cout << "Line " << i << std::endl;
    }
}

【讨论】:

  • 是的,这是一个不错的解决方案,但 OP 说他们不允许修改 main 函数。我想您可以将std::cout.imbue 放入静态初始化中,但我认为即使在技术上它也必须通过main 进行odr 使用才能实际执行。
  • 我同意,在main() 之外执行imbue 需要付出一些努力。在您编写时,应该可以在 OP 正在编写的任何函数/代码中进行一些静态初始化。
  • 为什么必须在 main 中使用 odr?
  • @LightnessRaceswithMonica 我在想eel.is/c++draft/basic#start.dynamic-4 以及是否可以强制在std::cout &lt;&lt; std::endl; 之前进行动态初始化,如果main 中只有这些的话。如果从来没有任何非初始化 odr-use,我不确定这意味着什么。
【解决方案2】:

如何定义类似于std::endl 的自己的函数(或函数模板):

std::ostream& enddash(std::ostream& os)
{
    // Can use std::endl in place of '\n' if flushing desired.
    os << "\n-\n";  
    return os;
}

使用示例:

int main(int argc, char* argv[]) {
    std::cout << "Hello, world." << enddash;
    std::cout << "Hello, world, once again" << enddash;
}

输出:

Hello, world.
-
Hello, world, once again
-

【讨论】:

  • 我很乐意,问题是他们给了我一个已经 std::cout
  • @Diego 他们是谁?
  • @DiegoEsquivel 这个任务是不可能的。任何人给你它都是没有意义的。
  • @Diego 难道你不能修改main方法或者整个main cpp文件吗?
  • 我只是不允许修改主要内容,我可以在主要内容中做任何我想做的事情,他们给了我 std::cout
【解决方案3】:

您可以使用endl 的宏定义来实现。

我不建议使用它,但如果你必须...

#include <iostream>
#include <string>

#define endl string("\n-\n") << std::flush

int main()
{
    std::cout << std::endl;
    return 0;
}

【讨论】:

  • 可悲的是未定义的行为,因为当至少包含一个标准库头文件时,不允许#define 标准库中使用的任何标识符。但我喜欢即使它被允许也会有多么错误。
  • 当定义不影响任何标准包含时它是否真的适用(比如,它没有在包含之前定义)?这有点不直观。
  • 是的,include 的顺序也不例外:eel.is/c++draft/macro.names#1 在实践中这不太可能导致任何问题,但可能不应该仅仅因为它有效而编写 UB 代码。 (无论如何,我认为我们都同意 #define endl 首先是一个坏主意,尽管它可能是实际可行的 OP 问题的最短解决方案。)
【解决方案4】:

这不像@NicholasM 的答案那么酷,但如果主要内容真的只包含std::cout &lt;&lt; std::endl;,则更容易:

#include <stdio.h> // We don't want any iostream stuff, so we use the C header.

namespace std
{
    struct dummy{};

    dummy cout;
    dummy endl;

    dummy& operator<<(dummy &d, const dummy& other){
        printf("\n-\n");
        return d;
    }
}

int main()
{
    std::cout << std::endl;
}

写这段代码感觉有点脏……

编辑:非常清楚:我不鼓励任何人以任何有意义的方式使用此代码(正如@uneven_mark 所说,它包含 UB)。在我看来,这个问题是作为某种噱头或谜语向 OP 提出的,因此我认为这样的问题有可能作为答案。

【讨论】:

  • 我并不感到惊讶,但究竟是什么?定义自己的std::cout?
  • std 命名空间中声明/定义任何内容,除了模板特化,对于特定实体没有特殊例外,如果包含任何标准库头文件,则为未定义行为。您包括&lt;stdio.h&gt;dummycoutendloperator&lt;&lt; 也不例外。
猜你喜欢
  • 2017-05-13
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 2021-08-04
  • 2011-01-13
  • 1970-01-01
  • 2020-07-23
相关资源
最近更新 更多