检测习语的工作原理如下
template<typename T, typename = void>
struct has_foo {static constexpr bool value = false;};
template<typename T>
struct has_foo<T, std::void_t<decltype(&T::foo)>> {static constexpr bool value = true;};
template<typename T>
constexpr bool has_foo_v = has_foo<T>::value;
然后我们可以检测到任何类型foo
中T
的存在。
if constexpr(has_foo_v<decltype(var)>)
var.foo();
我的问题是,键入的内容非常多(读取:想要键入很多键盘),我想知道以下是否可行
if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true)
var.foo();
不是。
这背后有原因吗? 更具体地说,如果允许这样做,还需要做出哪些权衡?
答案 0 :(得分:12)
从c ++ 17开始,如果你确实需要内联sfinae,总会有一个constexpr lambda解决方法:
#include <utility>
template <class Lambda, class... Ts>
constexpr auto test_sfinae(Lambda lambda, Ts&&...)
-> decltype(lambda(std::declval<Ts>()...), bool{}) { return true; }
constexpr bool test_sfinae(...) { return false; }
template <class T>
constexpr bool bar(T var) {
if constexpr(test_sfinae([](auto v) -> decltype(v.foo()){}, var))
return true;
return false;
}
struct A {
void foo() {}
};
struct B { };
int main() {
static_assert(bar(A{}));
static_assert(!bar(B{}));
}
答案 1 :(得分:6)
使用指向成员函数的指针是一个坏主意;如果foo
被重载,它就会失败(你有一个foo,但不只是一个)。谁真的想要&#34;你是否只有一个foo&#34;?几乎没有人。
这是一个简短的版本:
template<class T>
using dot_foo_r = decltype( std::declval<T>().foo() );
template<class T>
using can_foo = can_apply<dot_foo_r, T>;
,其中
namespace details {
template<template<class...>class, class, class...>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z, void, Ts...>;
现在,写dot_foo_r
有点烦人。
使用constexpr
lambdas,我们可以减少烦恼,并将其内联。
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
确实需要RETURNS
宏,至少在@ Barry提交[](auto&&f)RETURNS(f())
之前等同于[](auto&&f)=>f()
。
然后我们写can_invoke_f
,这是constexpr
的{{1}}变体:
std::is_invokable
这给了我们:
template<class F>
constexpr auto can_invoke( F&& f ) {
return [](auto&&...args)->std::is_invokable<F(decltype(args)...)>{
return {};
};
}
或使用@ Barry提出的C ++ 20语法:
if constexpr(
can_invoke([](auto&&var) RETURNS(var.foo()))(var)
) {
var.foo();
}
我们已经完成了。
诀窍是if constexpr(can_invoke(var=>var.foo())(var)) {
var.foo();
}
宏(或RETURNS
C ++ 20特性)让我们对表达式执行SFINAE。 lambda成为一种将表达式作为值传递的简单方法。
你可以写
=>
但我认为 [](auto&&var) ->decltype(var.foo()) { return var.foo(); }
值得(并且我不喜欢宏)。
答案 2 :(得分:1)
您还可以使用std::is_detected
来减少代码量。
在您的示例中,代码将如下所示:
template <class T>
using has_foo_t = decltype(std::declval<T>().foo());
if constexpr(std::is_detected_v<has_foo_t,decltype(var)>)
var.foo();