何时重载逗号运算符?


76

我经常在SO上看到有关在C ++中重载逗号运算符的问题(主要与重载本身无关,但类似序列点的概念),这让我感到奇怪:

什么时候应该使逗号超载?其实际用途有哪些例子?

我只是想不出我已经看到或需要的任何例子,例如

foo, bar;

在现实世界的代码中,所以我很好奇它何时被使用。


1
既然C ++具有统一的初始化语法,那么大多数这些技术都是不必要的。
2014年

Answers:


66

让我们将重点更改为:

当要重载逗号?

答案:从不。

例外:如果您正在执行模板元编程,请operator,在运算符优先级列表的最底部放置一个特殊位置,该位置对于构造SFINAE-guards等非常有用。

我所看到的关于重载的仅有的两个实际用途operator,都是在Boost中


93
永不说永不。
user541686 2011年

7
但是+1是例外。:P您介意对模板元编程的使用进行详细说明operator,吗?听起来真的很有趣。
user541686 2011年

2
另外,Boost.Parameter重载了逗号运算符,这是另一种用法。另外,我同意逗号运算符几乎永远不应过载。由于优先级较低,因此难以有效使用。
Paul Fultz II

2
您也可以在Eigen中找到它。
加布里埃尔·德格里穆阿德17'Jul

1
低优先级允许几乎所有可想像的表达式的合成,而无需额外的括号-这是该运算符的一个非常整洁的属性。它确实很方便,并且多年来我发现它有很多用途,使代码可读性和表达力...但是我的规则是仅在不引起任何意外并使代码的含义显而易见的情况下使用它没有阅读使用中的API文档的人。
Unslander Monica,

150

我使用逗号运算符来索引具有多个索引的地图。

enum Place {new_york, washington, ...};

pair<Place, Place> operator , (Place p1, Place p2)
{
    return make_pair(p1, p2);
}


map< pair<Place, Place>, double> distance;

distance[new_york, washington] = 100;

33
我实际上真的很喜欢+1。
ildjarn 2014年

12
另一方面,这是为了克服我们只能将一个参数传递给的事实operator[]。有人提出可以采用以下几个参数:参见Evolution Defect Report 88
Morwenn 2015年

2
多维数组实现也可以使用这种语法,但是不幸的是,对于整数类型,这种语法没有那么好的重载。
sim642

10
distance[{new_york, washington}]在不过载的情况下工作。额外的一组支架要付出很小的代价,才能避免发生如此邪恶的事情!
亚瑟塔卡

2
如果调用一个函数foo(new_york, washington),该函数应将两个单独的位置作为参数会发生什么?
OutOfBound

38

Boost.Assign使用它来执行以下操作:

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

而且我已经看到它用于古怪的语言破解,我看看是否能找到一些。


啊哈,我确实记得其中一种古怪的用法:收集多个表达式。(警告,黑暗魔法。)


1
嗯,找不到它。非常极端的东西。
GManNickG 2011年

1
但是说真的,您真的要编写这样的代码吗?对于阅读您的代码的人来说,这将非常令人困惑。我假设snippet是push_back这8个值中a的简写,但看起来好像9被添加到了vector<int>,这没有任何意义。坦率地说,对于Boost是“高质量库”,这是一个强烈的反驳。代码应该清晰明了。否则,也可以实现类似的功能T& operator--(int){ delete this; return *this; },也可能会很好地工作。对于其他人而言,并不明显。
戴蒙

2
好吧,按照常识,operator + =会在右侧添加表达式的值。在常见的理解中,表达式1,2,... 9计算为9。重载运算符颠覆了语义,尽管它在语法上是有效的,但这并不意味着它一定是好的。如果操作符重载可以使代码清晰明了,则很好,但是在这里,它会使代码模棱两可且令人困惑(至少在我看来)。它与C ++ 0x中的initializer_list赋值有很大不同,因为花括号使它立即显而易见了所发生的事情。此外,我认为向量的重载operator + =…
Damon

5
...可能不是最明智的选择之一,因为在向量上至少存在该运算符的两个相等有效的解释。我假设“在末尾添加元素”是这里的意思,但也可以是“使用这些参数在向量中的每个元素上调用运算符+ =”。可能只为相等大小的集合定义了它,或者可能对较小的集合进行了零扩展,或者……无论如何,如果不深入研究文档就不会知道,这并不明显。好的代码是显而易见的,无需解释。
戴蒙

2
再举一个例子,我记得几年前跨过一个字符串类,它重载了operator<=。这样您就可以编写像这样的很棒的代码str <= "foo";。当下一个阅读您的代码的人说“到底是什么?”时,这一点都不酷。并且第一次花一周的时间进行无益调试变得完全不酷,因为有人不知道并写了这样的东西if(str <= "bar")
戴蒙

24

逗号具有一个有趣的属性,它可以采用type参数void。如果是这种情况,则使用内置的逗号运算符。

当您要确定表达式的类型是否为void时,这很方便:

namespace detail_
{
    template <typename T>
    struct tag
    {
        static T get();
    };

    template <typename T, typename U>
    tag<char(&)[2]> operator,(T, tag<U>);

    template <typename T, typename U>
    tag<U> operator,(tag<T>, tag<U>);
}

#define HAS_VOID_TYPE(expr) \
    (sizeof((::detail_::tag<int>(), \
             (expr), \
             ::detail_::tag<char>).get()) == 1)

我让读者弄清楚正在发生的事情。记住operator,右边的那个。



9

SOCI-C ++数据库访问库中,它用于实现接口的入站部分:

sql << "select name, salary from persons where id = " << id,
       into(name), into(salary);

基本原理常见问题解答

问:重载的逗号运算符只是一种混淆,我不喜欢它。

好吧,请考虑以下几点:

“将查询X发送到服务器Y并将结果放入变量Z。”

在上方,“和”起逗号的作用。即使重载逗号运算符在C ++中不是很流行的做法,某些库也会这样做,从而获得简洁易懂的语法。我们非常确定,在SOCI中,逗号运算符被重载了,并且效果很好。


8

我使用逗号运算符来打印日志输出。它实际上与之非常相似,ostream::operator<<但是我发现逗号运算符实际上适合该任务。

所以我有:

template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }

它具有这些不错的特性

  • 逗号运算符的优先级最低。因此,如果您要流式传输表达式,则忘记括号不会使事情变得混乱。相比:

    myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
    myLog, "The result is: ", x&y;
    

    您甚至可以毫无问题地将比较运算符混在其中,例如

    myLog, "a==b: ", a==b;
    
  • 逗号运算符在视觉上很小。将许多东西粘合在一起时不会与阅读混淆

    myLog, "Coords=", g, ':', s, ':', p;
    
  • 它符合逗号运算符的含义,即“先打印”然后“先打印”。



5

同样,我收到了一个带有逗号运算符重载的github pull请求。看起来像以下

class Mylogger {
    public:
            template <typename T>
            Mylogger & operator,(const T & val) {
                    std::cout << val;
                    return * this;
            }
 };

 #define  Log(level,args...)  \
    do { Mylogger logv; logv,level, ":", ##args; } while (0)

然后在我的代码中我可以做:

 Log(2, "INFO: setting variable \", 1, "\"\n");

有人可以解释为什么这是好事还是坏事?


1
我不知道这是否不好。但避免编写如下代码:... << "This is a message on line " << std::to_string(__LINE__) << " because variable a = " << std::to_string(a) << " which is larger than " << std::to_string(limit) << "\n"。在报告错误或生成异常消息时非常常见。我不知道如果逗号是唯一的选择:任何其他运营商可能已经实现了这个,例如operator+operator|或者operator&&甚至operator<<本身。但这是一个有趣的案例。
alfC

4
我认为现代C ++会使用可变参数模板。
佩特2014年

回答有问题的问题很不好;-)
有限赎罪

4

一种实际用法是有效地将其与宏中的可变参数一起使用。顺便说一句,可变参数是GCC的早期扩展,现在是C ++ 11标准的一部分。

假设我们有一个class X,它将类型的对象添加A到其中。即

class X {
  public: X& operator+= (const A&);
};

如果我们想要1个或多个对象添加AX buffer;
例如,

#define ADD(buffer, ...) buffer += __VA_ARGS__

上面的宏,如果用作:

ADD(buffer, objA1, objA2, objA3);

然后它将扩展为:

buffer += objA1, objeA2, objA3;

因此,这将是使用逗号运算符的完美示例,因为变量参数会随之扩展。

因此,要解决此问题,我们需要重载comma运算符并将其包装+=如下

  X& X::operator, (const A& a) {  // declared inside `class X`
    *this += a;  // calls `operator+=`
  }

也许现在应该如此template<typename ... A> X& ADD(X& buff, A ... args) { int sink[]={ 0,(void(buff+=args),0)... }; return buff;}。注意:您可能必须防止使用(void) sink;语句来优化接收器。这闪避了宏,它是imo,甚至更好
WorldSEnder

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.