首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >std::充当自定义流操作器

std::充当自定义流操作器
EN

Stack Overflow用户
提问于 2012-10-22 00:35:36
回答 3查看 2.1K关注 0票数 10

我正在尝试使用C++11特性来使定制的流操作器更容易创建。我可以使用lambda函数作为操作器,但不能使用std::function<ostream&(ostream&)>

这是代码,简化如下:

代码语言:javascript
复制
#include <iostream>
#include <functional>
using namespace std;

auto lambdaManip = [] (ostream& stream) -> ostream& {
    stream << "Hello world" << endl;
};
function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& {
    stream << "Hello world" << endl;
};

int main (int argc, char** argv) {
    cout << lambdaManip;    // OK
    cout << functionManip;  // Compiler error
}

第二个cout语句失败如下:

代码语言:javascript
复制
g++-4 src/Solve.cpp -c -g -std=c++0x -o src/Solve.o -I/home/ekrohne/minisat
src/Solve.cpp: In function 'int main(int, char**)':
src/Solve.cpp:24:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ostream:579:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = std::function<std::basic_ostream<char>&(std::basic_ostream<char>&)>]'

为什么会失败呢?我用的是西格温gcc 4.5.3。

尽管我在问这个问题,但由于效率问题,我并不热衷于在任何地方使用std::function。但是,我确实希望编写返回lambda函数的函数,并且不知道如何在没有std::function的情况下这样做。例如,下面这样的内容会很好

代码语言:javascript
复制
auto getAdditionFunctor();

auto getAdditionFunctor() {
    return [] (int x, int y) { return x + y };
};

...but显然不起作用。是否有其他的语法起作用?我无法想象它会是什么样子,所以我可能被std::function困住了。

如果我能解决第二个问题,那么第一个问题就没有意义了。

谢谢。

定义operator<<(ostream&, std::function<ostream&(ostream&)>是有帮助的。我误读了一个网页,给人的印象是ostream足够聪明,可以把一个有operator()的任意物体当作操纵者。我搞错了。此外,我构建的简单lambda可能只是被编译成一个简单的旧函数,就像我被告知的那样。实际上,如果我使用变量捕获来确保lambda不是一个简单的函数,那么编译器就会失败。此外,定义了operator()的对象(默认情况下)不被视为操纵器:

代码语言:javascript
复制
class Manipulator {
    ostream& operator()(ostream& stream) const {
        return stream << "Hello world" << endl;
    };
} classManip;

function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& {
    return stream << "Hello world" << endl;
};

int main (int argc, char** argv) {
    const string str = "Hello world"; 
    auto lambdaManip = [&] (ostream& stream) -> ostream& {
        return stream << str << endl;     
    };

    cout << classManip;     // Compiler error
    cout << lambdaManip;    // Compiler error
    cout << functionManip;  // Compiler error
}

进一步更新:这是一个比下面的解决方案更健壮的解决方案,可以通过以下方法实现:

代码语言:javascript
复制
// Tell ostreams to interpret std::function as a
// manipulator, wherever it sees one.
inline ostream& operator<<(
        ostream& stream, 
        const function<ostream& (ostream&)>& manipulator) {
    return manipulator( stream );
}

这段代码有一个额外的const。我发现这是为了在我的项目中实际实现解决方案。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-10-22 00:59:09

如果您查看operator<< for ostream,获取std::function没有过载现象--这基本上就是您在使用cout << functionManip时要做的事情。要解决这个问题,可以自己定义重载:

代码语言:javascript
复制
ostream& operator<<(ostream& os, std::function<ostream& (ostream&)>& s)
{
    return s(os);
} 

或者将stream作为参数传递给函数:

代码语言:javascript
复制
functionManip(std::cout);

关于lambda工作的原因,考虑到lambda的返回类型未定义,并且使用函数指针存在重载:

代码语言:javascript
复制
ostream& operator<< (ostream& ( *pf )(ostream&));

lambda可能正在使用结构包装所有东西,并定义operator(),在本例中,将完全像函数指针一样工作。这对我来说是最有可能的解释,希望有人能纠正我,如果我错了。

票数 7
EN

Stack Overflow用户

发布于 2012-10-22 01:00:45

这不是导致错误的原因,但是由于您已经将lambdaManipfunctionManip定义为具有返回类型ostream&,所以我认为您忘记了将return stream;添加到两者中。

调用cout << functionManip失败,因为没有定义operator<<(ostream&, std::function<ostream&(ostream&)>。添加一个,调用就会成功。

代码语言:javascript
复制
ostream& operator<<(ostream& stream, function<ostream& (ostream&)>& func) {
  return func( stream );
}

或者,您也可以将functionManip称为

代码语言:javascript
复制
functionManip( cout );

这将在不添加operator<<定义的情况下工作。

至于您关于返回lambda的问题,由于getAdditionFunctor返回的lambda是一个无捕获的lambda,所以它可以隐式转换为一个函数指针。

代码语言:javascript
复制
typedef int(*addition_ptr)(int,int);
addition_ptr getAdditionFunctor()
{
  return [] (int x, int y) -> int { return x + y; };
}

auto adder = getAdditionFunctor();
adder(10,20); // Outputs 30
票数 5
EN

Stack Overflow用户

发布于 2016-01-20 23:57:37

前面的答案是正确的,但是如果您要在代码中大量使用std::functions和/或lambda作为流操作器,那么您可能只想在全局范围内将以下函数模板定义添加到代码库中:

代码语言:javascript
复制
template<class M>
auto operator<< (std::ostream& os, const M& m) -> decltype(m(os))
{
    return m(os);
}

那里的尾部返回类型使用表达式SFINAE,因此除非operator<<是一个格式良好的表达式,否则这个特定的operator<<重载甚至不会参与重载解析。(这就是你想要的。)

你甚至可以做这样的事情

代码语言:javascript
复制
template<class T>
auto commaize(const T& t)
{
    return [&t](std::ostream& os) -> std::ostream& {
        return os << t << ", ";
    };
}

int main()
{
    std::cout << commaize("hello") << commaize("darling") << std::endl;
}

(注意上面的代码中完全缺少任何std::function对象!)尽量避免将lambdas填充到std::function对象中,除非您必须这样做,因为构造std::function对象可能很昂贵;它甚至可能涉及内存分配。)

对于C++17来说,将这个operator<<重载添加到标准库中是有意义的,但我目前还不知道在这方面有任何具体的建议。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13003645

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档