为什么sfinae如果constexpr不允许?
检测成语的工作原理如下为什么sfinae如果constexpr不允许?
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;
,然后我们可以检测到任何类型的T
的foo
存在。
if constexpr(has_foo_v<decltype(var)>)
var.foo();
我的问题是,这是相当多输入(读:想砸我的键盘大量输入),我想知道是否以下是可能的
if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true)
var.foo();
这是不。
背后有一个原因吗?
更具体地说,如果这是允许的话,必须做出什么样的折衷?
您在使用指针的成员函数是一个坏主意;如果foo
被重载,它虚假地失败(你有一个foo,但不只是一个)。谁真的想要“你有一个富有”?几乎没有人。
这里是一个简短的版本:
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
lambda我们可以使它更少讨厌,并做到内联。
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
它确实需要RETURNS
宏,至少要等到@巴里的提交[](auto&&f)RETURNS(f())
相当于[](auto&&f)=>f()
。
然后,我们写can_invoke_f
,这是一个constexpr
变种std::is_invokable
:
template<class F>
constexpr auto can_invoke(F&& f) {
return [](auto&&...args)->std::is_invokable<F(decltype(args)...)>{
return {};
};
}
这给我们:
if constexpr(
can_invoke([](auto&&var) RETURNS(var.foo()))(var)
) {
var.foo();
}
或使用@巴里提出的C++ 20语法:
if constexpr(can_invoke(var=>var.foo())(var)) {
var.foo();
}
我们完成了。
诀窍是,RETURNS
宏(或=>
C++ 20功能)让我们对表达式执行SFINAE。拉姆达变成了一种简单的方式来表达这个表达式作为一个值。
你可以写
[](auto&&var) ->decltype(var.foo()) { return var.foo(); }
,但我认为RETURNS
是值得的(我不喜欢宏)。
由于C++ 17总是有constexpr拉姆达的解决方法,如果你真的需要做SFINAE在线:
#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{}));
}
请注意,'declval
@IgorR。你是对的,但我们有创造lambda'test_sfinae'将会测试的冒险。不过,我不得不考虑它,因为它远非完美。 –
@IgorR。我在'declval'中删除了引用,现在取决于lambda创建者,如果他想使用引用或值。如果复制构造函数被显式删除,lambda将仅在接受引用时匹配,例如'[](auto&v)','[](const auto&v)'或'[](auto && v)' –
您还可以使用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();
它的工作原理! :)虽然你确定这是C++ 17功能吗? –
@ W.F .:这是一个实验性功能。它还没有标准化。 – AndyG
[相关](https://meta.*.com/a/323382/2069064)。一个答案可能是因为这里没有替代,所以SFINAE如何?另一个答案可能是因为它没有被考虑,因为提案总是关于'如果'采取'bool'? – Barry
@Barry希望将问题编辑成可以有明确答案的表单。原来是想[顺着这样的答案](https://*.com/a/6623089/4832499) –