Consider the following example:
#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>();
}
When invoked with B
(line (*)
), the code does not compile anymore for std::declval
converts T
to an rvalue reference type in the specific case.
If we change it slightly as it follows, we have the opposite problem:
// ...
template<typename T>
auto f() -> decltype(std::declval<T&>().f())
{}
// ...
int main() {
f<A>();
f<B>();
// f<C>(); // (*)
}
Now line at (*)
won't work for std::declval
converts the type to an lvalue reference type in the specific case.
Is there any way to define an expression that accepts the type T
if it has a member function f
, no matter what's its reference qualifier?
I don't have any real case in which I would use that and I cannot make any real example of use.
This question is for the sake of curiosity, nothing more.
I understand that there is a reason if the ref-qualifier is there and I should not try to break the design of the class.
Build a type trait that returns true if expression declval<T>().f(declval<Args>()...)
is a valid call. Then pass in U&
and U&&
indicating an lvalue or rvalue object of type 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();
In C++17 this gets a bit simpler:
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&&>>>;
}