10

我正在尝试使用 C++11 功能使自定义流操纵器更易于创建。我可以将 lambda 函数用作操纵器,但不能使用std::function<ostream&(ostream&)>.

这是代码,归结为:

#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语句失败并显示以下内容:

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>&)>]'

为什么会失败?我正在使用 cygwin gcc 4.5.3。

虽然我在问,但std::function由于效率问题,我并不热衷于在任何地方使用。但我确实希望编写返回 lambda 函数的函数,并且不知道如何在没有std::function. 例如,像下面这样的东西会很棒

auto getAdditionFunctor();

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

...但显然不起作用。是否有另一种有效的语法?我无法想象它会是什么,所以我可能会被困住std::function

如果我对第二个问题有解决方案,那么第一个问题将毫无意义。


谢谢你。

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

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
}

进一步更新:事实证明,比以下解决方案更强大的解决方案可以通过以下方式完成:

// 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. 我发现这试图在我的项目中实际实施解决方案。

4

3 回答 3

7

如果您查看operator<<for ostream,则采取std::function- 并没有超载,这基本上就是您在这里尝试做的事情cout << functionManip。要解决此问题,请自行定义重载:

ostream& operator<<(ostream& os, std::function<ostream& (ostream&)>& s)
{
    return s(os);
} 

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

functionManip(std::cout);

至于为什么lambda起作用,鉴于 a 的返回类型lambda未定义,并且使用函数指针存在重载:

ostream& operator<< (ostream& ( *pf )(ostream&));

lambda 可能使用 aa 结构包装所有内容,并定义operator()在这种情况下哪个将完全像函数指针一样工作。这对我来说是最有可能的解释,如果我错了,希望有人能纠正我。

于 2012-10-22T00:59:09.660 回答
5

这不是错误的原因,但由于您已经定义了两者lambdaManip并且functionManip具有返回类型ostream&,我相信您忘记添加return stream;两者。


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

ostream& operator<<(ostream& stream, function<ostream& (ostream&)>& func) {
  return func( stream );
}

或者,您可以调用functionManip

functionManip( cout );

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


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

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
于 2012-10-22T01:00:45.290 回答
5

以前的答案正确地使用了最先进的技术,但是如果您要在代码中大量使用std::functions 和/或 lambdas 作为流操纵器,那么您可能只想在全局代码库中添加以下函数模板定义范围:

template<class M>
auto operator<< (std::ostream& os, const M& m) -> decltype(m(os))
{
    return m(os);
}

那里的尾随返回类型使用表达式 SFINAE,所以这个特定operator<<的重载甚至不会参与重载决议,除非m(os)是一个格式良好的表达式。(这就是你想要的。)

然后你甚至可以做类似的事情

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<<重载添加到标准库是有意义的,但我目前不知道该领域的任何具体建议。

于 2016-01-20T23:57:37.747 回答