首页 > 解决方案 > 在定义更受约束的版本之前和之后调用函数模板会产生奇怪的结果

问题描述

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

Run on gcc.godbolt.org

#include <concepts>
#include <iostream>

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

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

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

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

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

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

标签: c++language-lawyerc++20

解决方案


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

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

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

总结一下:

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

  • gcc 的正确之处在于两个不同的foo调用调用了两个不同的foo函数模板,但错误之处在于它破坏了两者相同,从而导致链接器错误。这是安腾 ABI #24

  • barMSVC 在for中的依赖于参数的查找中得到了这个错误,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';
}

然后clang编译(即它确实正确地从那个地方给你那个bar(42) == 1foo(42) == 22无论如何都会打印两次。

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


推荐阅读