为什么我的两个元组包含以相同方式创建的字符串,而不是相等的?

Aer*_*Sun 56 c++ tuples string-literals visual-c++

我正在使用 Microsoft Visual C++ 将以下程序编译为 C++20 程序:

#include <iostream>
#include <tuple>

int main()
{
    auto t1 = std::make_tuple("one", "two", "three");
    auto t2 = std::make_tuple("one", "two", "three");
    
    std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
    std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我看到以下输出:

(t1 == t2) is false
(t1 != t2) is true
Run Code Online (Sandbox Code Playgroud)

元组是相同的,那么为什么它的比较结果是错误的呢?我该如何解决?

Yak*_*ont 65

您正在比较指向字符缓冲区的指针,而不是字符串。

有时编译器会将两个不同的"one"s 转入同一个缓冲区,有时则不会。

在你的情况下,它不是。可能是调试版本。

添加#include <string_view>,然后

using namespace std::literals;

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);
Run Code Online (Sandbox Code Playgroud)

你会得到你所期望的。(在编译器中,使用<string>and""s代替<string_view>and ""sv)。

  • 我不禁想到这里的故事的寓意是“如果您不知道要分配什么类型,请不要使用‘auto’。” (18认同)
  • @chep 相反,`""` 是 C 遗留字符串,非常烦人。两个文本相同的文字在实现定义上是相等的这一事实是荒谬的。 (9认同)
  • @Deduplicator不,问题是`""`是一个数组文字,而数组文字上的`==`衰减为指针并比较指针,这都是C++中遗留的C cruft。合并字符串是一个转移注意力的事情;“hello”的地址应该与“7”的地址一样重要。衰减到指针在 C 语言中发明时是一种黑客行为,并且数组文字不比较“==”是一个缺失的功能;当他们知道后果时,没有人会用一种语言写出这样的内容。为了向后兼容,我们坚持使用它。 (4认同)

Gui*_*cot 37

的类型是"one"什么?这不是字符串,而是字符串文字。

您的问题基本上归结为以下代码:

char const* a = "one";
char const* b = "one";

std::cout << "(a == b) is " << std::boolalpha << (a == b) << "\n";
std::cout << "(a != b) is " << std::boolalpha << (a != b) << "\n";
Run Code Online (Sandbox Code Playgroud)

这很可能会输出相同的结果。

这是因为字符串文字将衰减为char const*. 比较两个指针比较它们在内存中的位置。现在这是一个你的编译器是否将字符串文字折叠成一个的问题。如果字符串文字被折叠,那么它们将相等,如果不是,它们将不相等。这可能因不同的优化级别而异。

那么你如何修正你的比较呢?

最好使用,std::string_view因为您似乎不需要拥有或更改其内容:

using namespace std::literals;

// ... 

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);
Run Code Online (Sandbox Code Playgroud)

std::string_view类是一个指针和一个大小的简单封装,并定义一个比较操作用于检查值相等。


zko*_*oza 13

该问题与 C++20 无关,而是来自字符串文字的实现方式。答案例如在这里:

为什么(仅)某些编译器对相同的字符串文字使用相同的地址?

简而言之,您的程序属于“未定义的 未指定行为”类别,因为它假定相同的 C 样式字符串文字具有相同的地址。这是因为像"a" == "a"比较地址这样的表达式,而不是内容。你的代码可以变得安全,可预测的,如果你使用的std::string文字,如"one"s"one"sv等,看https://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s

  • 我怀疑OP打算比较字符串地址...... (2认同)

lef*_*out 6

auto并不总是你的朋友。我认为在没有样板的情况下获得可靠的“正确”行为的正确方法是明确使用您知道具有值相等的类型。然后你也可以省略make_tuple并简单地使用初始化列表构造函数:

#include <string>
#include <tuple>
#include <iostream>

typedef std::tuple<std::string, std::string, std::string> StrTriple;

int main() {
  
  StrTriple t1{"one", "two", "three"};
  StrTriple t2{"one", "two", "three"};

  std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
  std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

毫无疑问,有些人会争辩说内存管理std::string会产生不必要的开销。string_view 可能更可取,但是在现实世界的应用程序中,字符串无论如何都需要在某处动态分配。

  • @Deduplicator `std::tuple` 不是一种类型,因此说它具有值相等是没有意义的。`tuple&lt;string,string,string&gt;` 可以,`tuple&lt;char*,char*,char*&gt;` 则不然——这两者都是类型,而 `std::tuple` 本身只是一个类型构造函数。 (2认同)