命名空间std中的小于重载函数

6
我很好奇为什么这段代码不起作用:
#include "stdafx.h"
#include <iostream>
#include <tuple>
#include <string>
#include <vector>
#include <algorithm>

typedef std::tuple<int, std::string> intString;
bool operator<(intString& lhs, intString& rhs){
    return std::tie(std::get<1>(lhs), std::get<0>(lhs)) < std::tie(std::get<1>(rhs), std::get<0>(rhs));
}

void printIntStrings(std::vector<intString>& v){
    for (intString& i : v){
        std::cout << std::get<0>(i) << " is " << std::get<1>(i) << std::endl;
    }
}

int main(int argc, char* argv[])
{
    std::vector<intString> v;
    v.push_back(std::make_tuple(5, "five"));
    v.push_back(std::make_tuple(2, "two"));
    v.push_back(std::make_tuple(9, "nine"));
    printIntStrings(v);
    std::sort(v.begin(), v.end());
    printIntStrings(v);
    return 0;
}

据我所理解,我需要创建一个intStrings的向量,然后我的操作员应该首先按元组中的第二个元素进行排序,因此输出应该是(无论如何是最后3行)。
5 five
9 nine
2 two

然而,在我的电脑上运行它,我得到了
2 two
5 five
9 nine

这意味着排序正在使用默认的小于运算符,忽略我指定的运算符。请注意,将const添加到参数之前似乎没有影响。

我找到了三种“修复”方法。

修复方法1:

在std命名空间中包围bool operator< ...,如下所示:

namespace std{
    bool operator<(intString& lhs, intString& rhs){
        return std::tie(std::get<1>(lhs), std::get<0>(lhs)) < std::tie(std::get<1>(rhs), std::get<0>(rhs));
    }
}

然而我被告知我们不应该向std命名空间中添加东西,因为这种行为是未定义的,所以这个修复方法似乎是最糟糕的。

修复方法 #2

像这样向元组中添加一些自定义内容:

enum class TRASH{DOESNTMATTER};
typedef std::tuple<int, std::string, TRASH> intString;
bool operator<(intString& lhs, intString& rhs){
    return std::tie(std::get<1>(lhs), std::get<0>(lhs)) < std::tie(std::get<1>(rhs), std::get<0>(rhs));
}

(并且很明显需要将TRASH :: DOESNTMATTER添加为第三个make_tuple参数)但是,针对如此简单的事情看起来需要付出很多工作。此外,由于枚举未被有意义地使用,因此似乎是浪费的。

修复方法 #3

使用谓词排序,如下所示:

std::sort(v.begin(), v.end(), operator<);

这似乎是最优雅的解决方案。然而,我不明白为什么我必须显式地告诉编译器使用我定义的operator<。

所以我想知道:

1)为什么会发生这种情况?C++难道不能找到我的实现并使用它吗?

2)哪种“修复”方法最好?如果我找不到任何一种,你会推荐哪种?

有什么想法吗?感谢您的阅读!


1
标准的 operator< 更匹配,因为它的参数是 const - Oktalist
@Oktalist 抱歉,忘记提到,在两个参数前添加 const 并没有帮助。我会进行编辑。 - Billy Won
问题在于元组已经有了一个operator<,所以编译器使用官方的而不是你的hack。 - Mooing Duck
2个回答

6

你的operator <重载在使用<的地方不可见(即在std::sort及/或其调用的任何辅助函数的正文中,在<algorithm>的某个位置)。

如果要使用它,则必须通过参数相关查找来获取;但在std::tuple<int, std::string>中没有任何与全局命名空间关联的命名空间,因此ADL也无法帮助您,只能使用标准的一个。

最简单的解决方法是将其作为比较器传递,最好使用lambda或函数对象(与函数指针相比,它们可以更好地进行内联),我还建议对其进行重命名;拥有完全不同于标准版本的operator <重载的语义,可能根据表达式a<b的使用位置而使用或不使用,这不是一个好主意。


好的,我想这就是那样的事情。有没有一种方法可以“强制”ADL来接受它?也许可以尝试使用namespace global之类的东西(我尝试过namespace global但不起作用)。 - Billy Won
1
@BillyWon 除非更改类型,否则无法做到。我也真的不建议编写一个与标准库提供的不同的“小于”运算符函数。 - T.C.
谢谢!将更改为更具描述性的名称。我将其命名为overloaded <,希望可以使用两个参数进行排序。最初是compareStringsFirst。 - Billy Won

-1

你已经自己修复了它

问题在于你的 operator< 函数没有覆盖默认的 tuple::operator<,它们在不同的命名空间中

因此,你的 Fix#1 和 Fix#3 都是好的解决方案

Fix#1 将它们放入相同的命名空间中使其正确覆盖,我认为这是最好的方法


并且会导致未定义的行为。是的,那绝对是最好的方式。 - T.C.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接