假设我有一个概念MyConcept
,它描述了遗传算法algo
的要求,在下面的示例代码中,唯一的要求是一个自由函数my_free_function
,它接受MyConcept
类型并返回相同的值,有三种类型满足这个要求。
template<class T>
concept MyConcept = requires (T a) {
{ my_free_function(a) } -> std::same_as<T>;
};
template<MyConcept T>
auto algo(T& a) {
return my_free_function(a);
}
namespace baz {
struct bar {};
bar my_free_function(bar) { return bar{}; }
}; // baz
unsigned long my_free_function(unsigned long) { return 42; }
namespace caz {
using NativeU64 = unsigned long;
NativeU64 my_free_function(NativeU64) { return NativeU64{42ul}; }
}; // caz
我面临的挑战是,为了让下面的调用编译为NativeU 64和unsigned long,我必须在概念和算法之前定义my_free_function。这是唯一的选择吗?我目前的代码组织和依赖结构依赖于能够在头文件中定义概念和算法,编译器在看到满足要求的定义之前使用这些头文件。
auto a = baz::bar{};
algo(a);
auto b = 1ul;
algo(b);
caz::NativeU64 c = 1ul;
algo(c);
编译器错误消息为:
“my_free_function(a)”将无效:调用函数“my_free_function”,该函数在模板定义中不可见,也无法通过参数相关查找{ my_free_function(a)} -〉std::same_as;
这与我所做的使它工作相匹配,但我一定错过了什么。
2条答案
按热度按时间xa9qqrwz1#
这就是特质被发明的根本原因:允许扩展那些 * 不能 * 使用ADL来扩展其功能的类型。概念不会改变这些规则。
如果你不能在概念之前声明这些函数,那么你就必须使用某种trait,默认trait可以调用自由函数(并使用一个概念来确保自由函数可用),而专门化版本可以调用任何它们想要的函数。
话虽如此,你的问题的起源已经......令人怀疑了。看,
unsigned long
并不属于你;它属于系统,* 任何人 * 都可以在任何时候,出于任何原因使用该类型,这意味着,只要某人可以访问MyConcept
,他们就可以询问MyConcept<unsigned long>
是否为真,而答案必须基于询问问题时可用的任何声明。因此,如果用户同时可以访问
MyConcept
* 和 *unsigned long
,那么在您没有为unsigned long
提供专门接口的情况下,用户能够提出这个问题是非常 * 危险 * 的。因为如果他们问这个问题而没有包含接口的声明,他们会得到错误的答案。如果你做了任何事情导致
MyConcept<T>
给予不同的答案,你的代码就是格式错误的(不需要诊断),就像违反了一个定义规则一样。你的代码应该让它“不可能”做到这一点。对于你定义的类型,这很容易:在每个定义之后,你都要包含
MyConcept
要寻找的接口,但是正如前面所说的,unsigned long
并不属于你;任何人都可以在任何时候使用它。因此,定义MyConcept
的同一个文件应该 * 也 * 定义MyConcept
依赖的任何默认功能。就像unsigned long
版本一样。pgccezyw2#
C找不到标识符“my_free_function”(有或没有概念)。
对于C-模板,应用Two-Phase-Lookup(即名称被查找两次):
1.在解析模板期间
1.在模板的示例化期间(在示例化点;兴趣点)
NativeU64
的情况类似于unsigned long
的情况。using NativeU64 = unsigned long;
不引入新类型;是一米三四的。当你在模板之前声明函数时,在解析模板时会发现它。另一个解决方案是使用:
在声明
my_free_function(unsigned int)
之后,因此模板永远不会用于unsigned long
。请参见“C++模板:完整指南,第2版“,特别是”14.3.1两相查找表“一章。