我觉得我需要什么
如何定义类型特征来检查某个类型的T
函数是否::foo(T)
已声明?
我发现很难::foo
以 SFINAE 友好的方式实现。例如,如果编译器已经到了定义以下内容的地步,
template<typename T>
void f(T t) { foo(t); }
foo
如果到现在还没有发现任何东西,那就太好了。
但是一旦我更改foo
为::foo
,就会出现硬错误。
用例(以防您认为我不需要上述内容)
我有一个这样的定制点:
// this is in Foo.hpp
namespace foos {
inline constexpr struct Foo {
template <typename T, std::enable_if_t<AdlFooable<std::decay_t<T>>::value, int> = 0>
constexpr bool operator()(T const& x) const {
return foo(x);
}
} foo{};
}
通过为所需类型foos::foo
定义 ADL重载,可以定制调用的行为。foo
该特征的定义很简单:
// this is in Foo.hpp
template <typename T, typename = void>
struct AdlFooable : std::false_type {};
template <typename T>
struct AdlFooable<T, std::void_t<decltype(foo(std::declval<T const&>()))>>
: std::true_type {};
鉴于
// this is in Bar.hpp
namespace bar {
struct Bar {};
bool foo(bar::Bar);
}
然后调用
// other includes
#include "Foo.hpp"
#include "Bar.hpp"
bool b = foos::foo(bar::Bar{});
工作正常,将foos::foo
调用路由到bar::foo(bar::Bar)
通过 ADL 找到的 。无论两个#include
s 的顺序如何,这都能很好地工作,这很好,因为如果传递地包含它们,它们可以按任意顺序包含// other includes
。
到目前为止,一切都很好。
我不太喜欢这种方法,因为人们可能会错误地定义下面的代码,而不是上面的第二个代码片段,
// this is in Bar.hpp
namespace bar {
struct Bar {};
}
bool foo(bar::Bar);
在全球范围内foo
。
在这种情况下,如果#include "Bar.hpp"
恰好在之前出现#include "Foo.hpp"
,则代码程序将会起作用,因为的主体Foo::operator()
将::foo(bar::Bar)
通过普通查找进行选择。
但是一旦#include
s 的顺序被颠倒过来,代码就会被破坏。
是的,这个错误是foo(bar::Bar)
在全局命名空间中进行定义的,但我认为这也是一个纯粹偶然不会被注意到的错误。
这就是为什么我想改变类型特征来表达“发现不合格的调用,但不是通过普通查找”foo(T)
,或者更直接地说,“foo(std::declval<T>())
必须编译,但不能::foo(std::declval<T>())
编译”。
实现此目的的常用方法是仅通过 ADL调用自定义实现。因此
::foo(bar::Bar)
,无论包含顺序如何,在任何情况下都不会考虑过载。而实现这一目标的方法是加入一颗毒丸:
普通的查找
foo
将foos::detail::foo
不起作用。它无法检查全局范围。因此,调用foo
将仅通过 ADL 进行查找。编辑:
这实际上告诉您如何形成只能通过 ADL 调用的特征
foo(x)
,即名义问题。也就是说,如果foos::foo(x)
可以通过此实现调用,则foo(x)
只能通过 ADL 调用。