libro improvements descargar book c++ c++17

c++ - improvements - ¿Por qué está encendido sfinae si constexpr no está permitido?



c++17 libro (3)

El idioma de detección funciona de la siguiente manera

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;

Y luego podemos detectar la presencia de foo en cualquier tipo T

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

Mi problema es que es mucho para escribir (lea: quiero romper mi teclado para escribir), y me pregunté si lo siguiente es posible

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

No lo es.

¿Hay una razón detrás de esto?
Más específicamente, ¿qué compensaciones se tendrán que hacer si esto se permitiera?


Desde c ++ 17 siempre hay una solución constexpr lambda si realmente necesita hacer sfinae en línea:

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

[demo en vivo]


Su uso de la función de puntero a miembro es una mala idea; si foo está sobrecargado, falla falsamente (tienes un foo, pero no solo uno). ¿Quién realmente quiere "tienes exactamente un foo"? Casi nadie.

Aquí hay una versión más breve:

template<class T> using dot_foo_r = decltype( std::declval<T>().foo() ); template<class T> using can_foo = can_apply<dot_foo_r, T>;

dónde

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...>;

Ahora, escribir dot_foo_r es un poco molesto.

Con constexpr lambdas podemos hacerlo menos molesto y hacerlo en línea.

#define RETURNS(...) / noexcept(noexcept(__VA_ARGS__)) / -> decltype(__VA_ARGS__) / { return __VA_ARGS__; }

Necesita la macro RETURNS , al menos hasta que la presentación de @ Barry a [](auto&&f)RETURNS(f()) sea ​​equivalente a [](auto&&f)=>f() .

Luego escribimos can_invoke_f , que es una variante constexpr de std::is_invokable :

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

Esto nos da:

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

o usando la sintaxis C ++ 20 propuesta por @ Barry:

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

y hemos terminado.

El truco es que la macro RETURNS (o la característica => C ++ 20) nos permite hacer SFINAE en una expresión. La lambda se convierte en una forma fácil de llevar esa expresión como un valor.

Usted podría escribir

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

pero creo que RETURNS vale la pena (y no me gustan las macros).


También puede reducir la cantidad de código usando std::is_detected .

En tu ejemplo, el código se vería así:

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