下面的示例不会崩溃,但使用 MSVC 编译器版本 19.32.31332 不打印任何内容,并使用 GCC 打印“def”:
#include <string>
#include <vector>
#include <set>
#include <ranges>
#include <iostream>
template <class R, class Value>
concept range_over = std::ranges::range<R> &&
std::same_as<std::ranges::range_value_t<R>, Value>;
const std::string& find1(const std::vector<std::string>& v)
{
return v[1];
}
template <range_over<std::string> Range>
std::reference_wrapper<std::string> find2(Range range)
{
return *(range.begin() + 1);
}
int main()
{
std::vector<std::string> v = { "abc", "def", "ghi" };
//find1 always prints "def"
//std::cout << find1(v);
std::cout << find2(v).get() << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但在我的现实应用程序中,类似的代码会因 MSVC 编译器版本 19.32.31332 崩溃。
代码正确吗?v打电话后有效吗func2(v)?
find2按值获取范围。v因此,您将返回对函数参数对象的引用,该对象是中向量的副本main,这本身很可能不是该函数的意图。例如,通过返回的引用进行的修改不会反映在v.
函数参数是否在函数返回时或在包含函数调用的完整表达式之后被销毁是由实现定义的。实际上,这将由所使用的 C++ ABI 决定(这也可能解释了 MSVC 和 GCC/Clang 之间的不同行为)。
因此,根据所使用的实现如何定义它,它可能有也可能没有未定义的行为。如果函数返回时参数对象被销毁,则调用operator<<将通过悬空引用读取值。否则它是一个有效的程序并将打印def。
在 C++17 之前,标准规定函数参数对象总是在函数返回时被销毁。CWG 第 1880 期对此进行了更改。然而,正如问题所指出的,实际的实现/ABI 确实具有(当时)在完整表达式末尾销毁它们的不合格行为,并且选择了决议中的实现定义,以便这些 ABI 不会休息。因此,实际上,该标准只是根据实际情况进行了调整。