Рассмотрим следующий пример:
#include <utility>
struct A { void f() {} };
struct B { void f() & {} };
struct C { void f() && {} };
template<typename T>
auto f() -> decltype(std::declval<T>().f())
{}
int main() {
f<A>();
// f<B>(); // (*)
f<C>();
}
Когда вызывается с B
(линия (*)
), код больше не компилируется для std::declval
новообращенные T
на тип ссылки rvalue в конкретном случае.
Если мы немного изменим его следующим образом, у нас возникнет противоположная проблема:
// ...
template<typename T>
auto f() -> decltype(std::declval<T&>().f())
{}
// ...
int main() {
f<A>();
f<B>();
// f<C>(); // (*)
}
Теперь линия на (*)
не будет работать для std::declval
преобразует тип в тип ссылки lvalue в конкретном случае.
Есть ли способ определить выражение, которое принимает тип T
если он имеет функцию-член f
независимо от того, что его эталонный классификатор?
У меня нет ни одного реального случая, в котором я бы использовал это, и я не могу привести ни одного реального примера использования.
Этот вопрос ради любопытства, не более того.
Я понимаю, что есть причина, если реф-классификатор есть, и я не должен пытаться нарушать дизайн класса.
Создайте черту типа, которая возвращает выражение true, если declval<T>().f(declval<Args>()...)
действительный звонок Тогда пройдите в U&
а также U&&
указание объекта типа lvalue или rvalue T
,
namespace detail{
template<class...>struct voider{using type=void;};
template<class... Ts>using void_t=typename voider<Ts...>::type;
template<template<class...> class, class=void, class...>
struct can_apply : false_type { };
template<template<class...> class L, class... Args>
struct can_apply<L, void_t<L<Args...>>, Args...> : true_type {};
template<class T>
using rvalue = decltype(declval<T>().f());
template<class T>
using lvalue = decltype(declval<T&>().f());
template<class T>
using can_apply_f
= integral_constant<bool, detail::can_apply<rvalue, void_t<>, T>{} ||
detail::can_apply<lvalue, void_t<>, T>{}>;
}
template<class T>
enable_if_t<detail::can_apply_f<T>{}>
f();
В C ++ 17 это становится немного проще:
namespace detail{
auto apply=[](auto&&g,auto&&...xs)->decltype(decltype(g)(g).f(decltype(xs)(xs)...),void()){};
template<class T>
using ApplyF = decltype(apply)(T);
template<class T>
using can_apply_f = std::disjunction<std::is_callable<ApplyF<T&>>, std::is_callable<ApplyF<T&&>>>;
}
Вы можете использовать Boost.Hana’s overload_linearly
протестировать оба варианта:
template<typename T>
using lval_of = add_lvalue_reference_t<decay_t<T>>;
auto call_f = hana::overload_linearly(
[](auto&& t) -> decltype(move(t).f()){},
[](auto&& t) -> decltype(declval<lval_of<decltype(t)>>().f()){}
);
template<typename T>
auto f() -> decltype(call_f(declval<T>()))
{}