如果不允许使用constexpr,为什么要使用sfinae?

时间:2017-06-06 16:21:28

标签: c++ c++17

检测习语的工作原理如下

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;

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

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

我的问题是,键入的内容非常多(读取:想要键入很多键盘),我想知道以下是否可行

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

不是。

这背后有原因吗? 更具体地说,如果允许这样做,还需要做出哪些权衡?

3 个答案:

答案 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{}));
}

[live demo]

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