在 B 类中声明为好友的 A 类成员模板函数无法访问 A 类的私有成员(仅限 Clang)

Class A member template function declared as friend in class B can't access private members of class A (Clang only)

本文关键字:成员 访问 Clang 仅限 声明 好友 函数      更新时间:2023-10-16

请查看此代码片段。我知道这没有多大意义,它只是为了说明我遇到的问题:

#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"的私有成员PrintAddress(thing(;source_file.cpp:42:6:注意:在函数模板的实例化中>此处请求的专业化'tBar::PrintDataAndAddress'酒吧打印数据和地址(tFo(((;//编译错误source_file.cpp:17:7:注意:此处声明为私有void PrintAddress(const T&thing(

但仅在Clang++中。GCC和MSVC对此很满意(您可以通过将代码粘贴到http://rextester.com/l/cpp_online_compiler_clang)

似乎tBar::PrintDataAndAddress<tFoo>(const tFoo&)正在使用与tFoo相同的访问权限,在那里它是朋友。我知道这一点是因为在tBar中与tFoo交朋友可以解决这个问题。如果tBar::PrintDataAndAddress是一个非模板函数,那么这个问题也会消失。

我在标准中找不到任何解释这种行为的内容。我相信这可能是对14.6.5-temp.inject的一个糟糕的解释,但我不能声称我已经阅读了所有内容

有人知道Clang没有编译上面的代码是否正确吗?如果是这样的话,你能引用相关的C++标准文本吗?

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

强制编译器在使用tBar::PrintDataAndAddress<tFoo>之前实例化它可以解决问题。

int main() 
{
    tBar bar;
    bar.PrintDataAndAddress(tWidget()); // Fine
    auto x = &tBar::PrintDataAndAddress<tFoo>; // <= make it work....
    bar.PrintDataAndAddress(tFoo()); // Now fine
   return 0;
}

这似乎是一个编译器提示,因为它看起来很像:

在C++中,为什么';是否可以使用另一个类的模板类型作为模板类成员函数的友元?

更确切地说。。。在bar.PrintDataAndAddress(tFoo());行中,编译器必须初始化成员函数tBar::PrintDataAndAddress<tFoo>,同时必须解析友元声明。这是内在的两个独立的步骤。显然,在一个表达式中编写时,编译器不会按正确的顺序执行。为了强制编译器首先通过访问函数指针来实例化bar.PrintDataAndAddress(tFoo()),这两个步骤的顺序是正确的。

试着在朋友函数之前添加这个

template <typename tFoo>