c++type-traitsfriend-functiontemplate-function

C++ template class, template member friend function matching rules


I have a templated class with a templated friend function declaration that is not having its signature matched when stated in a more direct, but seemingly equivalent, expression:

link to example on online compiler

#include <type_traits>

template <typename Sig> class Base;
template <typename R, typename ... Args> class Base<R(Args...)> { };
template <typename Sig, typename T> class Derived;
template <typename Sig> struct remove_membership;

template <typename T, typename R, typename ... Args>
class Derived<R(Args...), T> : public Base<R(Args...)> {
  static void bar() { }

  // XXX: why are these two not equivalent, and only the 1st version successful?
  template <typename T2>
  friend auto foo(T2 const &) -> Base<typename
    remove_membership<decltype(&std::remove_reference_t<T2>::operator())>::type> *;
  template <typename T2>
  friend auto foo(T2 const &) -> Base<R(Args...)> *;
};

template <typename F, typename R, typename ... Args>
struct remove_membership<R (F::*)(Args...) const> {
  using type = R(Args...);
};

template <typename T>
auto foo(T const &) -> Base<typename 
remove_membership<decltype(&std::remove_reference_t<T>::operator())>::type> * 
{
  using base_param_t = typename remove_membership<
    decltype(&std::remove_reference_t<T>::operator())>::type;
  Derived<base_param_t, T>::bar();
  return nullptr;
}

int main(int, char **) { foo([](){}); } // XXX blows up if verbose friend decl. removed.

Inside member definitions of Derived<R(Args...), T> (for example, in the body of bar()), the types match, adding to my confusion:

static_assert(std::is_same<Base<R(Args...)>, Base<typename  
  remove_membership<decltype(&std::remove_reference_t<T>::operator())>::type>>::value,
  "signature mismatch");

Are there rules around template class template member function (and friend function) delarations and instantiations that make these preceding declarations distinct in some or all circumstances?


Solution

  • template <typename T2>
    void foo(T2 const &)
    
    template <typename T2>
    auto foo(T2 const &)
    -> std::enable_if_t<some_traits<T2>::value>;
    

    Are 2 different overloads. Even if both return void (when valid).
    2nd overload uses SFINAE.

    (and yes, template functions can differ only by return type contrary to regular functions).

    Your version is not identical but similar (&std::remove_reference_t<T>::operator() should be valid)

    You can use the simpler template friend function:

    template <typename T, typename R, typename ... Args>
    class Derived<R(Args...), T> : public Base<R(Args...)> {
      static void bar() { }
    
      template <typename T2>
      friend auto foo(T2 const &) -> Base<R(Args...)>*;
    };
    
    template <typename T>
    auto foo(T const &) -> Base<void()>* // friend with Derived<void(), U>
    {
      using base_param_t = typename remove_membership<
        decltype(&std::remove_reference_t<T>::operator())>::type;
      Derived<base_param_t, T>::bar();
      return nullptr;
    }
    

    Demo

    but you have then to implement different version of the template foo.