在定义更受约束的版本之前和之后调用函数模板会产生奇怪的结果

我的同事今天向我展示了以下示例:

Run on gcc.godbolt.org

#include <concepts>
#include <iostream>

template <typename T>
void foo(T)
{
    std::cout << "1n";
}

template <typename T>
void bar(T value)
{
    foo(value);
}

void foo(std::same_as<int> auto)
{
    std::cout << "2n";
}

在这里,bar(42);并分别foo(42);打印12如果您只调用其中之一。

如果您同时调用两者(按此顺序),则:

  • 叮当印1 1
  • GCC 发出链接器错误,抱怨foo.
  • MSVC2 2在发布版本中打印并在调试版本中发出类似的链接器错误(可能受增量链接的影响,未调查)。

这里发生了什么?代码在我看来格式良好,但也许我错了?

回答

凉爽的。每个编译器都是错误的。

在 内bar,对 的调用foo(value)仅具有foo<T>在范围内可见的不受约束。因此,当我们调用 时foo(value),唯一可能的候选者是 (1) 一 (2) 任何依赖于参数的查找找到的。由于T=int在我们的示例中,并且int没有关联的命名空间,因此 (2) 是一个空集。因此,当bar(42)调用 时foo(42),这foo是无约束模板,应打印 1。

在另一方面,内mainfoo(42)有两种不同的重载需要考虑:受约束的一个不受约束的一个。受约束的一种是可行的,并且比不受约束的更受约束,所以这是首选。所以从内部main()foo(42)应该调用foo(same_as<int> auto)应该打印 2的约束。

总结一下:

  • Clang 犯了这个错误,因为它显然缓存了foo<int>调用,这是不正确的 - 另一个foo重载不是专业化,它是一个重载,需要单独考虑。

  • gcc 是正确的,因为两个不同的foo调用调用了两个不同的foo函数模板,但错误的是它破坏了相同的内容,因此我们最终会出现链接器错误。这是安腾 ABI #24。

  • MSVC 在barfor 中的依赖于参数的查找中得到了这个错误,foo(value)找到了以后声明的foo.

更有趣的是,如果您将函数更改为constexpr int而不是void,这可以让您在编译时验证此行为......如:

#include <concepts>
#include <iostream>

template <typename T>
constexpr int foo(T)
{
    return 1;
}

template <typename T>
constexpr int bar(T value)
{
    return foo(value);
}

constexpr int foo(std::same_as<int> auto)
{
    return 2;
}

static_assert(bar(42) == 1);
static_assert(foo(42) == 2);

int main()
{
    std::cout << bar(42) << 'n';
    std::cout << foo(42) << 'n';
}

然后铛编译(即它正确地给你bar(42) == 1,并foo(42) == 2从该点),但然后打印2反正两次。

虽然 gcc 仍然可以编译,但只是有相同的链接器错误,因为它对两个函数模板的处理相同。


以上是在定义更受约束的版本之前和之后调用函数模板会产生奇怪的结果的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>