如果不允许使用constexpr,为什么sfinae在上面

Why is sfinae on if constexpr not allowed?

本文关键字:为什么 sfinae 在上面 constexpr 如果不 不允许 如果      更新时间:2023-10-16

检测成文的工作原理如下

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();

不是。

这背后有原因吗?
更具体地说,如果允许这样做,必须进行哪些权衡?

以来C 17,如果您确实需要对sfinae进行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{}));
}

[live demo]

您将指针用于成员函数是一个坏主意。如果foo被超载,则会伪装失败(您有一个FOO,而不仅仅是一个(。谁真的想要"你有一个foo&quot&quot?几乎没有人。

这是一个英国人版本:

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,这是std::is_invocableconstexpr变体:

template<class F>
constexpr auto can_invoke( F&& f ) {
  return [](auto&&...args)->std::is_invocable<F(decltype(args)...)>{
    return {};
  };
}

这给了我们:

if constexpr(
  can_invoke([](auto&&var) RETURNS(var.foo()))(var)
) {
  var.foo();
}

或使用 @barry提出的C 20语法:

if constexpr(can_invoke(var=>var.foo())(var)) {
  var.foo();
}

我们完成了。

诀窍是RETURNS宏(或=> C 20功能(使我们可以在表达式上进行sfinae。lambda成为一种将表达方式作为价值携带的简便方法。

你可以写

    [](auto&&var) ->decltype(var.foo()) { return var.foo(); }

,但我认为RETURNS值得(我不喜欢宏(。

您还可以使用std::experimental::is_detected

减少代码量。

在您的示例中,代码将看起来像:

template <class T>
using has_foo_t = decltype(std::declval<T>().foo());
if constexpr(is_detected_v<has_foo_t,decltype(var)>)
  var.foo();