通过“ tuple”和“ tie”实现比较运算符,好主意吗?


98

(注:tupletie可以从升压或C ++ 11获得。)
当只有两个元素编写小结构,我有时往往选择std::pair,因为所有重要的东西是该数据类型已经完成,像operator<严格弱排序。
缺点是几乎没有用的变量名。即使我自己创建了那个typedef,也不会在两天后记住确切的first含义second,尤其是当它们属于同一类型时。对于两个以上的成员,情况变得更糟,因为嵌套pair非常糟糕。
另一个选择是tuple,无论是Boost还是C ++ 11,但看起来并没有更好和更清晰。因此,我自己开始编写结构,包括所有需要的比较运算符。
由于特别operator<麻烦,我想到了仅依靠为定义的操作来规避整个混乱tuple

的示例operator<,例如用于严格弱排序:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

(从传递的参数中引用tie一个。) tupleT&


编辑:从@DeadMG私下继承的建议tuple不是一个坏建议,但是它有很多缺点:

  • 如果操作员是独立的(可能是朋友),我需要公开继承
  • 通过强制转换,operator=可以轻松绕过我的函数/运算符(特别是)
  • 有了tie解决方案,我可以省去某些成员,如果他们对订购无所谓

我需要考虑此实现中的任何缺点吗?


1
对我来说看起来很合理……
ildjarn 2011年

1
即使没有成功,这也是一个非常聪明的主意。我将不得不对此进行调查。
templatetypedef

这看起来很合理。我现在唯一想到的陷阱是tie不能应用于位域成员。
伊势紫藤

4
我喜欢这个主意!如果tie(...)要在各种运算符(=,==,<等)中重复调用,则可以编写一个私有的内联方法make_tuple(...)来对其进行封装,然后从其他各个位置进行调用,例如return lhs.make_tuple() < rhs.make_tuple();(尽管返回类型为该方法可能很有趣!)
aldo

13
@aldo:C ++ 14可以解救!auto tied() const{ return std::tie(the, members, here); }
Xeo

Answers:


61

当然,这比编写自己的运算符更容易编写正确的运算符。我要说的是,如果性能分析表明比较操作是应用程序中非常耗时的部分,则仅考虑使用其他方法。否则,维护它的难易程度应超过任何可能的性能问题。


17
我无法想象那里的情况下tuple<>operator<将是比任何一个手写的速度慢。
ildjarn 2011年

51
我曾经有一个完全相同的想法,并做了一些实验。看到编译器内联并优化了与元组和引用有关的所有内容,并发出几乎与手写代码完全相同的汇编,我感到非常惊讶。
JohannesD

7
@JohannesD:我可以支持那个证词,曾经做过同样的一次
sehe

这是否保证严格的弱排序?怎么样?
CinCout

5

我遇到了同样的问题,我的解决方案使用了c ++ 11可变参数模板。代码如下:

.h部分:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

基本情况下的.cpp不带参数:

bool lexiLessthan()
{
  return false;
}

现在您的示例变为:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);

我在这里输入了类似的解决方案,但不需要!=运算符。 stackoverflow.com/questions/11312448/…–
steviekm3

3

在我看来,您仍然没有解决解决问题的std::tuple方法,也就是说,您必须知道每个成员变量的数量和名称,并在函数中重复两次。您可以选择private继承。

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

这种方法一开始有点麻烦,但是您只需要将变量和名称保留在一个位置,而不是每个要重载的运算符都保留在每个位置。


3
因此,我将使用诸如此类的变量的命名访问器T& one_member(){ return std::get<0>(*this); }?但是那不需要我为我拥有的每个“成员”提供这种方法,包括const和非const版本的重载吗?
Xeo

@Xeo我认为命名访问器不需要比创建实际变量更多的工作。无论哪种方式,您都必须为每个变量使用一个单独的名称。我想const / non-const会有重复。但是,您可以模板化所有这些工作。
Lee Louviere

1

如果您打算使用多个运算符重载,或者使用来自tuple的更多方法,则建议使tuple成为类的成员或从tuple派生。否则,您正在做的工作还很多。在两者之间做出选择时,需要回答的一个重要问题是:您希望您的课程成为元组吗?如果没有,我建议包含一个元组并通过使用委派来限制接口。

您可以创建访问器以“重命名”元组的成员。


我将OP的问题理解为“ operator<使用std::tie合理的方式实施我的课程” 吗?我不明白这个答案与那个问题有什么关系。
ildjarn 2011年

@ildjarn有些评论我没有在这里发表。我已经编译了所有内容,因此读起来更好。
Lee Louviere
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.