C++模板class、模板成员友元函数匹配规则

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

我有一个模板化的 class 和一个模板化的友元函数声明,当用更直接但看似等价的表达式声明时,它的签名没有匹配:

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.

Derived<R(Args...), T> 的内部成员定义(例如,在 bar() 的正文中),类型匹配,增加了我的困惑:

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

是否有关于模板 class 模板成员函数(和友元函数)声明和实例化的规则,使这些前面的声明在某些或所有情况下都不同?

问题可以简化为:

template<class T>
struct identity {
    using type=T;
};

class X {
    int bar();
public:
    template<class T>
    friend T foo();
};

template<class T>
typename identity<T>::type foo() { return X{}.bar(); }

int main() {
    foo<int>(); // error: bar is a private member of X
}

即使 我们 知道 identity<T>::type 总是 T,编译器并不知道这一点并且这样假设是错误的。 identity<T> 代码后面的某个地方可能有一个专门化,解析为 T.

以外的某种类型

因此,当编译器看到 foo 的第二个声明时,它不会假定它是之前声明的同一个朋友 foo

template <typename T2>
void foo(T2 const &)

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

是2个不同的重载。即使两者 return void(有效时).
第二次重载使用 SFINAE。

(是的,与常规函数相反,模板函数只能在 return 类型上有所不同)。

您的版本不相同但相似(&std::remove_reference_t<T>::operator() 应该有效)

您可以使用更简单的模板好友功能:

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

但您必须实施不同版本的模板 foo