c++ - 在定义更受约束的版本之前和之后调用函数模板会产生奇怪的结果
问题描述
我的同事今天向我展示了以下示例:
#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);
1
2
如果您同时调用两者(按此顺序),则:
- 铿锵印
1
1
。 - GCC 发出一个链接器错误,抱怨
foo
. - MSVC
2
2
在发布版本中打印并在调试版本中发出类似的链接器错误(可能受到增量链接的影响,未调查)。
这里发生了什么?代码在我看来格式正确,但也许我错了?
解决方案
凉爽的。每个编译器都是错误的。
在 内bar
,对 的调用foo(value)
仅在范围内具有不受约束的foo<T>
可见性。因此,当我们调用 时foo(value)
,唯一可能的候选者是 (1) 一 (2) 任何与参数相关的查找找到的。因为T=int
在我们的示例中,并且int
没有关联的命名空间,所以 (2) 是一个空集。结果,当bar(42)
调用时foo(42)
,foo
就是不受约束的模板,它应该打印 1。
另一方面, withinmain
有foo(42)
两个不同的重载需要考虑:受约束的和不受约束的。受约束的是可行的,并且比不受约束的更受约束,所以这是首选。所以从内部main()
,应该调用应该打印 2foo(42)
的 constrained 。foo(same_as<int> auto)
总结一下:
Clang 出错了,因为它显然缓存了
foo<int>
调用,这是不正确的 - 另一个foo
重载不是专业化,它是重载,需要单独考虑。gcc 的正确之处在于两个不同的
foo
调用调用了两个不同的foo
函数模板,但错误之处在于它破坏了两者相同,从而导致链接器错误。这是安腾 ABI #24。bar
MSVC 在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) == 1
)foo(42) == 2
但2
无论如何都会打印两次。
虽然 gcc 仍然可以编译,但只是有相同的链接器错误,因为它同样破坏了两个函数模板。
推荐阅读
- sql - 带/不带“ORDER BY”的 CosmosDB sql 查询返回不同数量的项目
- telegraf - Telegraf - 输入尾部插件错误(错误:度量解析错误:偏移处的预期字段)
- transactions - 使用带有 mySql 和 Oracle 的 atomikos 的分布式事务 (XA) 不工作
- sql - 带有 db4free php my admin 的 Visual Basic 登录表单
- java - 自定义列表视图适配器 OnRelease 事件
- css - 如何使用css从浏览器添加悬停效果
- influxdb - 使用 Grafana 定制仪表板
- angular - 从下拉列表中的json数组中删除重复值?
- flutter - 如何在 Flutter 中创建带有圆角的模态底页?
- c# - Oracle Managed Data Access 底层提供程序在打开时失败