有没有办法用一行在同一个对象上调用多个函数?

Posted

技术标签:

【中文标题】有没有办法用一行在同一个对象上调用多个函数?【英文标题】:Is there a way to call multiple functions on the same object with one line? 【发布时间】:2017-02-11 19:37:39 【问题描述】:

只是想整理一个程序,想知道是否有人可以给我一些关于在同一行上多次调用一个队列上的成员函数的语法糖。

例如,改变:

queue<int> q;
q.push(0);
q.push(1);

类似于:

q.(push(0), push(1));
//or
q.push(0).push(1);

我知道这看起来有点荒谬,而且不实用。但是,如果我想缩短这样的一小部分代码,是否可以选择这样做?从我目前阅读的内容来看,只有当函数具有非void 返回值时,才可能链接方法。

当然,这是一个选项:

q.push(0); q.push(1);

但我试图避免出现两次q。再次......语法糖:)

这里的目标不是初始化,而是压缩一个对象/容器在一段代码中被调用的次数。我引用队列的原因是因为它是动态的。

【问题讨论】:

使 q.push() 返回对 q 的引用。 使用初始化列表? 如果 >=C++11: queue&lt;int&gt; q0,1 此样式名为Fluent interface。但要非常小心执行顺序。 我实际上想知道为什么我们没有一个很好的规范来解决这个问题(关于c++ 标签)。好吧,这个将来可能会用作副本。 【参考方案1】:

如果你有一个可以修改的类,让函数返回一个对自身的引用:

template<typename T>
class queue 
public:
    //...
    queue& push(T data) 
        //...
        return *this; //return current instance
    
    //...
private:
    //...
;

那你就可以了

queue<int> q;
q.push(0).push(1);

如果你不能,那么你的双手就被束缚了。您可以对类进行包装,但为了节省一些字符,这几乎不值得。

在您使用push 的情况下,您可以这样做:

queue<int> q =  0, 1 ;

但这显然只适用于push,因为在两次推送之后队列将包含01

【讨论】:

"push(1) 可以在 push(0) 之前执行" @Barry 但是评估的顺序是未指定的,所以它可以。 q.push(f()).push(g()) 中,f()g() 的评估是未指定的,但q.push(f()) 肯定必须在第二次调用push() 之前执行。 q.push(0).push(1) 类似于push(push(q, 0), 1)。显然,函数的参数是在开始执行之前计算出来的。 @MattGalaxy:从未指定过。不可能,因为需要调用第一次推送的结果才能评估第二次推送。【参考方案2】:

你总是可以只定义一个包装器,比如

template< class Item >
void push( queue<Item>& q, std::initializer_list<Item> const& values )

    for( Item const& v : values )  q.push( v ); 

然后这样称呼它:

push( q, 1, 2, 3 );

如果你想要的不是符号方便,而只是使用流畅的接口技术,那么如果你不能修改类,定义一个操作符:

template< class Item >
auto operator<<( queue<Item>& q, Item v )
    -> queue<Item>&
 q.push( move( v ) ); return q; 

然后这样称呼它:

q << 1 << 2 << 3;

请务必记录您的同事尝试掌握代码的过程。 :)

哦,好吧,仍然,如果你不能修改类,你当然可以这样做:

template< class Item >
struct Fluent

    queue<Item>& items;

    auto push( Item v )
        -> Fluent&
     items.push( move( v ) ); return *this; 

    Fluent( queue<Item>& q ): items( q ) 
;

然后这样称呼它:

Fluent( q ).push( 1 ).push( 2 ).push( 3 );

免责声明:编译器未触及任何代码。

玩得开心!

【讨论】:

【参考方案3】:

这里只是为了好玩,这是一个小模板技巧,它提供了一种链接几乎所有方法的方法,忽略返回值:

// The struct providing operator()(...) so that a call is simply
// chainer_t_instance(param_for_call1)(param_for_call2)(param_for_call3);
template <typename Class, typename Method>
struct chainer_t

    chainer_t(Class& instance, Method&& method) :
        _instance(instance),
        _method(method)
    

    chainer_t(chainer_t&& chainer) :
        _instance(chainer._instance),
        _method(chainer._method)
    

    // Avoid copy to avoid misunderstanding
    chainer_t(const chainer_t&) = delete;    
    chainer_t& operator=(const chainer_t&) = delete;

    // Operator () takes anything
    template <typename... Types>
    chainer_t& operator()(Types&&... types)
    
        (_instance.*_method)(std::forward<Types>(types)...);
        return *this;
    

protected:
    Class& _instance;
    Method& _method;
;

// Just to ease the writting
template <typename Class, typename Method>
chainer_t<Class, Method> chain(Class& instance, Method&& method)

    using chainer = chainer_t<Class, Method>;
    return chainer(instance, std::forward<Method>(method));

链式调用将是:

chain(my_instance, &my_class::add)(1)(2)(3)(4);

Live example

【讨论】:

如果你问我更像函数组合,链接需要this().that().the_other(thing) @cat 函数组合需要调用第二个函数来获取第一个函数的输出,所以不是这样。但是“链接”有点误导,是的。 哦,对了,语法让我忘记了我们正在尝试保留对象【参考方案4】:
auto repeat_call = [](auto&& f)
  return y_combinate(
    [f=decltype(f)(f)](auto&& self, auto&&...args)->decltype(self)
      f( decltype(args)(args)... );
      return decltype(self)(self);
    
  );
;

y_combinate being a y combinator.

现在我们可以repeat_call( [&amp;](int x) q.push(x); )(1)(0);

【讨论】:

【参考方案5】:

如果不能修改类,仍然可以使用逗号:

#include<queue>
#include<iostream>

int main() 
    std::queue<int> q;
    (q.push(0), q).push(1);
    std::cout << q.size() << std::endl;

【讨论】:

【参考方案6】:

这可能不是您要寻找的,但请不要忘记,C++ 不是基于行的语言(// cmets 除外)。

因此,将多个简短的语句放在一行中是完全合理的。从而达到:

在同一个队列上多次调用成员函数 行。

你只需要改变:

queue<int> q;
q.push(0);
q.push(1);

进入:

queue<int> q;
q.push(0); q.push(1);

不,它不会删除两次键入 q,但如果这是一个问题,我怀疑你的问题更有可能是名称过长的变量。假设是这种情况,请记住您可以使用引用来为变量提供更简单的本地句柄:

auto &foo = a_really_long_name_for_a_queue;
foo.push(0); foo.push(1);

【讨论】:

请不要这样写代码。如果有人试图将您的“一行”包含在 if 语句中,它可以解决零问题,但代价高昂,既难以阅读,也可能引入错误...... @Barry 我非常尊重您对此的看法,而且我确实相信多语句行会使代码更难阅读。但是我也相信如果仔细使用它实际上可以提高可读性,尽管这是一个非常有争议的观点。我个人不相信要花太多精力来确保那些在没有正确阅读代码的情况下更改代码的人不会导致自己出错。但同样,这是一个非常基于意见的问题,并且意识到很多人会不同意我的观点。 @vality - 我很高兴我不在你工作的地方工作。我无法想象在我的编码团队中有一个不“相信付出太多努力”来使他/她的代码可读的人。当然,修改您的代码的人应该阅读它。但是故意加入一种增加其他人误读可能性的编码风格是不负责任的。每行一个语句是编码风格文档中非常常见的要求,因为它增强了可读性。此外,编译器错误和转储跟踪通常是面向行的,使您的代码更难调试和更难阅读。

以上是关于有没有办法用一行在同一个对象上调用多个函数?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法在异步函数中等待多个等待调用?

有没有办法用 C# 在 Visual Studio 中调用已经存在的表单的函数

Azure 函数 - 防止多次调用

R在同一引用类对象的多个实例上调用函数

linux 文件IO操作中,用read函数读取文件,有没有办法每次只读取一行,而不是读取指定的字节数?

第16天 匿名函数,递归,二分法,内置函数