【问题标题】:Class A member template function declared as friend in class B can't access private members of class A (Clang only)在 B 类中声明为友元的 A 类成员模板函数无法访问 A 类的私有成员(仅限 Clang)
【发布时间】:2017-03-14 00:24:36
【问题描述】:

请看一下这段代码 sn-p。我知道这没有多大意义,只是为了说明我遇到的问题:

#include <iostream>
using namespace std;

struct tBar
{
    template <typename T>
    void PrintDataAndAddress(const T& thing)
    {
        cout << thing.mData;
        PrintAddress<T>(thing);
    }

private:
    // friend struct tFoo; // fixes the compilation error

    template <typename T>
    void PrintAddress(const T& thing)
    {
        cout << " - " << &thing << endl;
    }
};

struct tFoo
{
    friend void tBar::PrintDataAndAddress<tFoo>(const tFoo&);
    private:

    int mData = 42;
};

struct tWidget
{
    int mData = 666;
};

int main() 
{
    tBar bar;
    bar.PrintDataAndAddress(tWidget()); // Fine

    bar.PrintDataAndAddress(tFoo()); // Compilation error

    return 0;
}

以上代码触发如下错误:

source_file.cpp:10:3: 错误:'PrintAddress' 是 'tBar' 的私有成员 打印地址(事物); source_file.cpp:42:6: 注意:在函数模板的实例化中 >specialization 'tBar::PrintDataAndAddress' 在此处请求 bar.PrintDataAndAddress(tFoo()); // 编译错误 source_file.cpp:17:7: 注意:这里声明为私有 void PrintAddress(const T& 事物)

但仅在 Clang++ 中。 GCC 和 MSVC 都可以使用(您可以通过将代码粘贴到 http://rextester.com/l/cpp_online_compiler_clang 中来快速测试)

似乎tBar::PrintDataAndAddress&lt;tFoo&gt;(const tFoo&amp;) 使用与tFoo 相同的访问权限,在此成为朋友。我知道这一点是因为在tBar 中与tFoo 成为朋友可以解决这个问题。如果tBar::PrintDataAndAddress 是非模板函数,问题也会消失。

我无法在标准中找到任何解释此行为的内容。我认为这可能是对 14.6.5 - temp.inject 的错误解释,但我不能声称我已经阅读了所有内容。

有谁知道 Clang 未能编译上述代码是否正确?如果是这样,您能否引用相关的 C++ 标准文本?

似乎要发生这个问题,被访问的私有成员需要是一个模板函数。例如,在上面的例子中,如果 我们将 PrintAddress 设为非模板函数,代码将编译不会出错。

【问题讨论】:

  • 也许我遗漏了一些东西,但在我看来,clang 不编译这个是完全明智的。我觉得奇怪的是,朋友声明被注释掉了,导致它编译。在我看来,无论如何它都不应该编译。过去我遇到过很多情况,其中 gcc 在涉及大量模板的友谊方面存在“过于宽松”的错误,我认为就是这种情况。
  • @NirFriedman 为什么你认为 Clang 不编译它是明智的?这对我来说似乎违反直觉。 A 类允许朋友访问 B 类成员函数(在本例中为模板函数),因此它可以访问 A 的私有和受保护成员,但为什么会删除对 B 的私有/受保护成员的访问?
  • 对我来说看起来像一个铿锵虫。正在搜索以查看是否已被举报。
  • 主干上仍然中断。报告为llvm.org/bugs/show_bug.cgi?id=30859,略短版本的@aschepler 的repro。

标签: c++ c++11 templates clang++


【解决方案1】:

强制编译器在使用之前实例化tBar::PrintDataAndAddress&lt;tFoo&gt; 可以解决问题。

int main() 
{
    tBar bar;
    bar.PrintDataAndAddress(tWidget()); // Fine

    auto x = &tBar::PrintDataAndAddress<tFoo>; // <= make it work....

    bar.PrintDataAndAddress(tFoo()); // Now fine

   return 0;
}

这似乎是一个编译器问题,因为它看起来与此非常相似:

In C++, why isn't it possible to friend a template class member function using the template type of another class?

更准确一点...在bar.PrintDataAndAddress(tFoo()); 行中,编译器必须实例化成员函数tBar::PrintDataAndAddress&lt;tFoo&gt;,同时它必须解析友元声明。那是内部两个单独的步骤。显然,当用一个表达式编写时,编译器并没有按照严格的顺序执行此操作。要强制编译器首先通过访问函数指针来实例化bar.PrintDataAndAddress(tFoo()),这两个步骤的顺序正确。

【讨论】:

  • +1 用于寻找解决方法。您的理论并不能完全解释我们看到的行为(例如,如果 tBartFoo 成为朋友或 PrintAddress 不是模板函数,则此问题已修复),但这是迄今为止我们所拥有的最接近的东西。跨度>
【解决方案2】:

尝试在朋友功能之前添加它

template <typename tFoo>

【讨论】:

  • 不幸的是,这并不能解决问题。事实上,情况更糟,因为现在 PrintDataAndAddress 的所有特化都会有同样的问题,导致 bar.PrintDataAndAddress(tWidget());bar.PrintDataAndAddress(tFoo()); 触发编译错误(在它可以编译为 tWidget 特化之前)
  • 你使用的是什么类型的编译器?
  • 如开篇问题所述,Clang。您可以使用在线 Clang 编译器自己测试问题。单击此处查看@aschepler 提供的上述代码的精简版本:rextester.com/YBBQ69572
  • 似乎当 void PrintAddress(const T& thing) 公开时,使用 Clang 编译器没有错误
  • 嗯,当然,但我要问的问题是为什么在这种情况下无法访问私有成员(如 PrintAddress)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-22
  • 1970-01-01
  • 1970-01-01
  • 2017-04-20
  • 2012-11-15
相关资源
最近更新 更多