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