如何定义一个类型特征来检查对于类型
T
是否声明了函数 ::foo(T)
?
我发现很难的是以 SFINAE 友好的方式拥有
::foo
。例如,如果编译器已达到定义以下内容的程度,
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{};
}
允许通过为所需类型定义 ADL
foos::foo
重载来自定义对 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
将呼叫路由至通过 ADL 找到的 bar::foo(bar::Bar)
。无论两个 #include
的顺序如何,这都可以很好地工作,这很好,因为如果 // 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
的顺序恰好颠倒,代码就会崩溃。
是的,这个错误在于在全局命名空间中定义
foo(bar::Bar)
,但我认为这也是一个错误,它可以纯粹偶然地被忽视。
这就是为什么我想更改类型特征来表达“找到对
foo(T)
的不合格调用,但不是通过普通查找”,或者,更直接地说,“foo(std::declval<T>())
必须编译,但是::foo(std::declval<T>())
必须不编译”。
执行此操作的普通方法是通过 ADL only 调用自定义实现。因此,无论包含顺序如何,在任何情况下都不会考虑重载
::foo(bar::Bar)
。
做到这一点的方法是添加毒丸:
namespace foos {
namespace detail {
void foo() = delete;
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);
}
};
}
inline constexpr detail::Foo foo{};
}
普通查找
foo
会找到foos::detail::foo
,这是行不通的。它无法检查全局范围。因此,foo
调用将仅通过ADL查找。