c++c++11templatesdecltyperef-qualifier

Trailing return type, declval and reference qualifiers: can they work together?


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.


Solution

  • 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&&>>>;
    }