为什么在头文件中使用私有内部类作为参数会出错,而在另一种情况下在调用函数中会出错?



我正在尝试构建一个模板化的c++函数,该函数接受指向内部类对象的指针作为其参数。(我在这个问题上遇到的一个早期困难可以在"当涉及依赖作用域的内部类时,如何使c++找到模板化的函数头?"中看到。)有一次,我遇到了一个问题,后来发现是访问控制问题;我已经知道如何避免这个问题,但我仍然有点不明白为什么这个问题是这样报告的(这让我在追踪问题方面陷入了困境)。

基本上,我试图调用函数,私有内部类对象作为参数,从friend类中的方法(因此拥有所需的访问权限)。当然,问题在于非方法函数是而不是一个friend,因此不能访问内部类。但是编译器将其报告为头文件不匹配,而不是访问问题。

首先,设置。我的数据类具有以下一般结构:

template <typename T>
class Outer
{
  private:
  struct Inner
  {
    T val;
    Inner (T v) : val(v) { }
  };
  public:
  Inner* ptr = nullptr;
  friend class Other;
};

如果我写一个简单的函数,不是模板,并试图把一个Inner*作为参数,编译器正确地诊断出这个问题:

void testUntemplated (const Outer<int>::Inner* p)
{
  cout << p->val << endl;
}

甚至不调用这个函数,一旦它被定义,g++告诉我error: ‘struct Outer<int>::Inner’ is private, clang++告诉我error: 'Inner' is a private member of 'Outer<int>'。了不起的。

但是现在考虑这个模板化的函数,从类内部调用:

template <typename T>
void testFunction (const typename Outer<T>::Inner* p)
{
  cout << p->val << endl;
}
struct Other {
  template<typename T>
  static void main(const Outer<T>& foo)
  {
    testFunction<T>(foo.ptr);  //this line is flagged as the error
  }
};

即使Outer已经被定义,并且Inner是私有的——不管用来填充T的类型名是什么——函数定义没有错误地传递;在这种情况下,标记的是对testFunction调用g++中的error: no matching function for call to ‘testFunction(Outer<int>::Inner* const&)’是非常无用的消息。令人高兴的是,clang++做得更好一点:它用错误标记了同一行(error: no matching function for call to 'testFunction'),但在此之后附有解释性说明(note: candidate template ignored: substitution failure [with T = int]: 'Inner' is a private member of 'Outer<int>')。

但是,即使更有帮助的clang++已经找到了我所看到的实际错误来源(定义的函数无法访问其参数的类型),它仍然说错误在于调用,而不是定义,我已经了解到这种不匹配通常意味着我发现了c++的一些角落,我不理解以及我应该(特别是当不同的编译器有相同的看似奇怪的错误行为)。这里到底发生了什么?为什么它不能像拒绝常规函数一样拒绝模板化函数呢?

考虑以下代码,它是完全合法的c++,编译和运行都很好:

#include <iostream>
template <typename T>
class Outer
{
private:
    struct Inner { };
};
template <typename T>
void testFunction (const typename Outer<T>::Inner* p)
{
    std::cout << "Hello!" << std::endl;
}
template<>
class Outer<double>{
public:
    struct Inner { };
};
int main()
{
    Outer<double>::Inner i;
    testFunction<double>(&i);
    return 0;
}

编译器不能在Outer<T>::Inner上发出诊断,因为它不知道Outer后面是否会有显式专门化,使函数格式良好。当您稍后尝试调用testFunction<int>时,SFINAE开始从过载集中删除格式错误的函数签名(因为Inner是私有的),从而给您一个"不匹配的函数"错误。

然而,当你在你的函数定义中写Outer<int>::Inner(如在你的testUntemplated中),这将触发一个隐式实例化,除非之前声明了Outer<int>的显式专门化。编译器不需要担心以后的显式特化,因为标准要求在任何可能触发隐式实例化(§14.7.3 [temp.exp .spec]/p6)之前声明显式特化:

如果是模板,则是成员模板或类模板的成员明确专门化,那么应该声明专门化在第一次使用专门化之前中的每个翻译单元都要进行隐式实例化发生这种用法的;不需要诊断。

最后,请注意,如果没有实例化(§14.6 [temp.res]/p8),则编译器不需要为没有有效特化的模板生成诊断:

如果不能为模板生成有效的专门化,那么模板未实例化,模板格式错误,否诊断需要。

你的主要问题:

为什么使用私有内部类作为参数在头文件中是错误的,而在调用函数中是错误的?

当编译器处理Other::main中的以下行时,

testFunction<T>(foo.ptr); 

它尝试创建testFunction的实例。此时,发现T::InnerT的一个私有类。问题仍然是与testFunction。在第二种情况下,遇到问题时正在处理的行在Other::main中。

相关内容

最新更新