为什么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; 

,然后我们可以检测到任何类型的Tfoo存在。

if constexpr(has_foo_v<decltype(var)>) 
    var.foo(); 

我的问题是,这是相当多输入(读:想砸我的键盘大量输入),我想知道是否以下是可能的

if constexpr(std::void_t<decltype(&decltype(var)::foo)>(), true) 
    var.foo(); 

这是不。

背后有一个原因吗?
更具体地说,如果这是允许的话,必须做出什么样的折衷?

+9

[相关](https://meta.*.com/a/323382/2069064)。一个答案可能是因为这里没有替代,所以SFINAE如何?另一个答案可能是因为它没有被考虑,因为提案总是关于'如果'采取'bool'? – Barry

+0

@Barry希望将问题编辑成可以有明确答案的表单。原来是想[顺着这样的答案](https://*.com/a/6623089/4832499) –

您在使用指针的成员函数是一个坏主意;如果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是值得的(我不喜欢宏)。

+2

只考虑包括这种情况作为下一个草案中另一种激励的例子。 '如果constexpr(can_invoke_f (x => x.foo())){...}'与OP所要求的相比仍然不太理想,但并不差。 – Barry

+0

@Barry摆脱了“decltype”并改进了'can_invoke'; 'can_invoke'现在接受一个函数实例'f'并在'f'上返回'constexpr' invoke-tester。弗雷尔*偷取/适应;我认为现在是非常轻松的。 – Yakk

+3

什么是'=>'语法? –

由于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{})); 
} 

[live demo]

+0

请注意,'declval ()'是一个左值引用,所以如果你测试的函数不接受非const的左值引用,测试将失败。 –

+0

@IgorR。你是对的,但我们有创造lambda'test_sfinae'将会测试的冒险。不过,我不得不考虑它,因为它远非完美。 –

+1

@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(); 
+0

它的工作原理! :)虽然你确定这是C++ 17功能吗? –

+1

@ W.F .:这是一个实验性功能。它还没有标准化。 – AndyG