16

Please take a look to this code snippet. I know it does not make much sense, it is just intended to illustrate the problem I am encountering:

#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;
}

The code above triggers the following error:

source_file.cpp:10:3: error: 'PrintAddress' is a private member of 'tBar' PrintAddress(thing); source_file.cpp:42:6: note: in instantiation of function template >specialization 'tBar::PrintDataAndAddress' requested here bar.PrintDataAndAddress(tFoo()); // Compilation error source_file.cpp:17:7: note: declared private here void PrintAddress(const T& thing)

but only in Clang++. GCC and MSVC are fine with it (you can quickly test that by pasting that code in http://rextester.com/l/cpp_online_compiler_clang)

It seems as if tBar::PrintDataAndAddress<tFoo>(const tFoo&) is using the same access as tFoo, where it is befriended. I know this because befriending tFoo in tBar fixes this issue. The problem also goes away if tBar::PrintDataAndAddress is a non-template function.

I have not been able to find anything in the Standard that explains this behavior. I believe it could be a bad interpretation of 14.6.5 - temp.inject, but I can't claim I have read all of it.

Does anyone know if Clang is right failing to compile the above code? Can you please quote the relevant C++ standard text if that is the case?

It seems that for this problem to happen, the private member being accessed needs to be a template function. e.g., in the example above, if we make PrintAddress a non-template function, the code will compile without errors.

StefanM
  • 797
  • 1
  • 10
  • 17
TheProgammerd
  • 223
  • 1
  • 6
  • Maybe I'm missing something, but it seems to me completely sensible that clang does not compile this. What I find strange is that the friend declaration commented out, causes this to compile. It seems to me like it should not compile regardless. I've encountered quite a few cases in the past where gcc has "too-lenient" bugs with regards to friendship where a lot of templating is involved, I think that's the case here. – Nir Friedman Oct 31 '16 at 20:52
  • @NirFriedman why do you think it is sensible that Clang does not compile that? It seems counter-intuitive to me. class A gives friend access to a class B member function (template function in this case) so it has access to A's private and protected members, but why would that remove access to B's private/protected members? – TheProgammerd Oct 31 '16 at 20:57
  • Looks like a clang bug to me. Searching to see if it's already reported. – aschepler Oct 31 '16 at 20:59
  • @NirFriedman: If I understand the OP correctly, `void PrintAddress(const tBar& thing)` compiles (with a suitable adjustment to the friendship of course). Why is that? – Martin Bonner supports Monica Oct 31 '16 at 20:59
  • I suggest raising it as a Clang bug. You'll probably get an explanation of why it isn't a bug (if it isn't). – Martin Bonner supports Monica Oct 31 '16 at 21:00
  • Sorry, I misread your code, I thought you were calling `PrintAddress` directly in the second line of main. – Nir Friedman Oct 31 '16 at 21:02
  • 3
    A trimmed down version: http://rextester.com/YBBQ69572 – aschepler Oct 31 '16 at 21:05
  • Also note in that trimmed down version, *removing* the friend declaration avoids the bug, which is insane. – aschepler Oct 31 '16 at 21:08
  • @MartinBonner I don't have a llvm bugzilla account (just requested one), so I can't report one. In the meanwhile, if anyone with an account wants to report it, please go ahead and comment on this question with the response – TheProgammerd Oct 31 '16 at 21:26
  • 2
    Still breaks on trunk. Reported as https://llvm.org/bugs/show_bug.cgi?id=30859 with a slightly shortened version of @aschepler's repro. – T.C. Oct 31 '16 at 22:30

2 Answers2

6

Forcing the compiler to instantiate tBar::PrintDataAndAddress<tFoo> before using it solves the problem.

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

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

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

   return 0;
}

It seems to be a compiler promlem as it looks quite similar to this:

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

To be a little bit more precise... In the line bar.PrintDataAndAddress(tFoo()); the compiler has to instanciate the memberfunction tBar::PrintDataAndAddress<tFoo> and at the same time it has to resolve the friend declaration. That are internaly two seperate steps. Apparent the compiler doesn't do it in the rigth order when written in one expression. To force the compiler to instantiate bar.PrintDataAndAddress(tFoo()) first by access to the function pointer these two steps are in the right order.

Community
  • 1
  • 1
Tunichtgut
  • 301
  • 1
  • 6
  • +1 for finding a workaround. Your theory does not fully explain the behavior we see (like the fact that this is fixed if `tBar` befriends `tFoo` or if `PrintAddress` is not a template function), but is the closest thing we have so far. – TheProgammerd Nov 01 '16 at 16:13
-3

Try adding this before the friend function

template <typename tFoo>
felit
  • 13
  • 3
  • That does not fix the problem, unfortunately. In fact, it makes it worse, since now all specializations of PrintDataAndAddress will have the same problem, causing both `bar.PrintDataAndAddress(tWidget());` and `bar.PrintDataAndAddress(tFoo());` to trigger the compilation error (before it would compile fine for the tWidget specialization) – TheProgammerd Oct 31 '16 at 21:07
  • What type of compiler are you using? – felit Oct 31 '16 at 21:15
  • As stated in the opening question, Clang. You can use an online Clang compiler to test the problem yourself. Click here for a trimmed-down version of the code above provided by @aschepler: rextester.com/YBBQ69572 – TheProgammerd Oct 31 '16 at 21:19
  • It seems that when void PrintAddress(const T& thing) is made public there is no error using Clang complier – felit Oct 31 '16 at 21:33
  • 1
    Well, of course, but the question I am asking is why private members (like PrintAddress) can't be accessed in that context – TheProgammerd Oct 31 '16 at 21:40