Почему 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;

И тогда мы можем обнаружить присутствие foo в любом типе T,

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

Моя проблема в том, что набирать текст довольно много (читай: хочу много раз набирать мою клавиатуру), и я подумал, возможно ли следующее

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

Это не.

Есть ли причина этого?
Более конкретно, какие компромиссы должны быть достигнуты, если это будет разрешено?

3 ответа

Решение

Использование указателя на функцию-член - плохая идея; если 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 лямбды, мы можем сделать это менее раздражающим и сделать это встроенным.

#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 inline:

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

[живое демо]

Вы также можете уменьшить объем кода с помощью 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();
Другие вопросы по тегам