【问题标题】:std::function as a custom stream manipulatorstd::function 作为自定义流操纵器
【发布时间】:2012-10-11 19:16:37
【问题描述】:

我正在尝试使用 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&lt;&lt;(ostream&amp;, std::function&lt;ostream&amp;(ostream&amp;)&gt; 有帮助。我误读了一个网页,并认为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。我发现这试图在我的项目中实际实施解决方案。

【问题讨论】:

  • 您的意思是在那些操纵器中省略“返回”吗?
  • 我已经读到这些是隐含的,添加它们没有帮助。在这种情况下,我认为它们在返回时更具可读性,但在没有返回时它们是合法的。毕竟,如果我注释掉cout &lt;&lt; functionManip;,一切都会正常工作。
  • @EdKrohne : return 在这里是 not 可选的——你的 lambda 调用 UB 通过具有非void 返回类型而不返回值,就像任何其他功能。 §6.6.3/2:“从函数的末尾流出等效于没有值的return;这会导致返回值函数中的未定义行为。”看起来工作正常只是 UB 的一种可能表现形式。
  • @ildjarn 谢谢。又误读了一个网站。

标签: c++ lambda c++11 iostream manipulators


【解决方案1】:

如果您查看operator&lt;&lt; 中的ostream,则使用std::function 并没有过多的负担——这基本上就是您在这里尝试使用cout &lt;&lt; functionManip 所做的事情。要解决此问题,请自行定义重载:

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

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

functionManip(std::cout);

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

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

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

【讨论】:

  • 这种行为不是偶然的,它是标准规定的。不捕获任何内容的 lambda 总是 可以隐式转换为普通的旧函数指针。 (反之亦然:确实捕获任何东西的 lambda 从不隐式转换为函数指针。)
【解决方案2】:

这不是错误的原因,但由于您已将 lambdaManipfunctionManip 定义为具有返回类型 ostream&amp;,我相信您忘记将 return stream; 添加到两者。


调用cout &lt;&lt; functionManip 失败,因为没有定义operator&lt;&lt;(ostream&amp;, std::function&lt;ostream&amp;(ostream&amp;)&gt;。加一个,调用就成功了。

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

或者,您可以将functionManip 称为

functionManip( cout );

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


关于返回 lambda 的问题,由于getAdditionFunctor 返回的 lambda 是一个无捕获的 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

【讨论】:

  • 好点,我应该为我的玩具示例考虑这一点。但是,如果我正在捕捉呢?变量捕获是从函数返回lambda 的点;如果我不想进行变量捕获,我会首先使用一个普通的旧函数。
  • +1。我不知道无捕获 lambda 可以隐式转换为函数指针,但它非常有意义。
【解决方案3】:

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

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

那里的尾随返回类型使用expression SFINAE,因此这个特定的operator&lt;&lt; 重载甚至不会参与重载决议,除非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 对象!除非绝对必要,否则尽量避免将 lambda 填充到std::function 对象中,因为构造std::function 对象可能很昂贵;它甚至可能涉及内存分配。)

C++17 将这个 operator&lt;&lt; 重载添加到标准库是有意义的,但目前我不知道该领域有任何具体建议。

【讨论】:

  • 我想我找到了原因,为什么它不在标准中...... o.O(或者我搞砸了,这总是可能的......)。我将模板 operator 之后的项目全局标头中,从那时起,它似乎试图接管项目中的所有 operator
  • @BitTickler:表达式 SFINAE 在旧版本的 MSVC 中存在问题,但仅此而已。我怀疑你在某个地方有错误。如果您发布一个 godbolt.org 链接(例如 this 但您的代码已损坏),我可以看看。
  • 抱歉 - 在规范示例中无法重现。我想在另一个方向上取得进展,所以我在遇到麻烦的第一个迹象时就放弃了这种方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-26
  • 2015-06-02
  • 1970-01-01
  • 2010-10-22
  • 2010-10-06
相关资源
最近更新 更多