16

In C++11, they made it possible to friend a template parameter simply with friend T. You can also friend methods within that parameter with friend T::Method().

However, how do you friend a template parameter's constructor?

class BeMyFriend
{
public:
 BeMyFriend& operator=(const BeMyFriend& rhs) = default;
 BeMyFriend(const BeMyFriend& rhs) = default;
};

template<class T>
class Test
{
 friend T& T::operator=(const T&); //Works fine, no error
 friend T::T(const T&); //error: prototype for 'BeMyFriend::BeMyFriend(const BeMyFriend&)' does not match any in class 'BeMyFriend'
};

int main()
{
 Test<BeMyFriend> hmm;

 return 0;
}

I'm able to friend the template parameter's operator= just fine, but I'm unable to friend T::T(const T&).

How can I make friend T::T(const T&); work?


edit: This appears to be a different issue than what is solved in Make Friend the constructor of a template class. The issue there is dealing with circular template parameters in a declaration. It doesn't deal with the constructor of an actual template parameter.

The type of the Foo is a normal templated class, not a template parameter like T in my example. Something like friend Foo<B>::Foo<B>() from that submission should compile just fine, unlike the issue I'm having here with friend T::T(const T&).

edit: In case this ends up mattering, I'm compiling with gcc 7.2.

edit: I also want to clarify that C++ does support making constructors friends. For example, friend X::X(char), X::~X(); in the first example at http://en.cppreference.com/w/cpp/language/friend.

The issue here is how to make a template parameter's constructor a friend.

irfna
  • 525
  • 1
  • 4
  • 13
  • Possible duplicate of [Make Friend the constructor of a template class](https://stackoverflow.com/questions/32285085/make-friend-the-constructor-of-a-template-class) – Jean-Baptiste Yunès Apr 05 '18 at 14:44
  • @Jean-BaptisteYunès There's a lot of other weirdness going on in that submission, and the answer doesn't seem to deal with the problem I have here – irfna Apr 05 '18 at 14:47
  • 1
    Yes, the compiler literally says that cannot find a match for `BeMyFriend::BeMyFriend(const BeMyFriend&)` _and_ proposes `BeMyFriend::BeMyFriend(const BeMyFriend&)` as a candidate o_O Fwiw, VS 2015 says `error C2039: 'T': is not a member of 'BeMyFriend'` – jdehesa Apr 05 '18 at 14:53
  • I'm curious if making a `using` of the constructor `T::T(const T&)` then friending that instead would fix the problem, but I have no idea how to get the type of a constructor... – irfna Apr 05 '18 at 14:55
  • In C++ constructors have no names but friend declaration requires a name. So, it does not seem to be possible. Although it could use syntax similar to `using T::T`, e.g. `friend T::T` to make all constructors of `T` friends. – Maxim Egorushkin Apr 05 '18 at 16:54
  • @MaximEgorushkin That doesn't work either. Attempting `friend typename T::T` (it doesn't accept just `friend T::T`) gives the error "error: no type named 'T' in 'class BeMyFriend'" – irfna Apr 05 '18 at 17:17
  • @irfna It does not, as I said. – Maxim Egorushkin Apr 05 '18 at 17:21
  • I tried `friend T&::T(const T&);` it does not throw an error, I can't guarantee any undesirable aftermaths ... – Abr001am Apr 06 '18 at 16:50
  • Also same as `friend T&::T();` doesn't , i just "guess" because when you instantiate a friend class you need to pass it by reference to the variable template, waiting what would be the possible justification of C++ lawyers. – Abr001am Apr 06 '18 at 16:55
  • This seems like a compiler error, because adding `friend BeMyFriend::BeMyFriend(BeMyFriend const&);` to the definition of `Test` doesn't cause any problems. Also, the syntax you are using matches how cppreference says to befriend a constructor. I think the fact that there's no return type for a constructor is tripping up the parsing logic. I'm not sure though. Clang and GCC both give an error here. – SJL Apr 06 '18 at 16:55
  • @Abra001 That's very interesting, although gcc is giving me a warning about it "warning: friend declaration 'T& T(const T&)' declares a non-template function [-Wnon-template-friend] friend T&::T(const T&); //error: prototype for 'BeMyFriend::BeMyFriend(const BeMyFriend&)' does not match any in class 'BeMyFriend'" – irfna Apr 06 '18 at 17:03
  • @Abra001 I just tested, and as expected it doesn't actually properly friend the constructor. When calling a private function, it throws an error saying it is private in that context. Really creative find though, I have no idea why that compiles or what it is trying to do. – irfna Apr 06 '18 at 17:07
  • well... i didn't diregard that... aftermath :D i'm looking any other workarround – Abr001am Apr 06 '18 at 17:10
  • @irfna @Abra001: The `T&` attempt is declaring and befriending a function named `T` in the global namespace—the `::` is the _unary_ scope resolution operator. – Davis Herring Apr 06 '18 at 23:56
  • @DavisHerring irfna the templating friendship works well [see here](https://ideone.com/4eQaDM) wheras i don't know if it fits the op's needs. – Abr001am Apr 07 '18 at 00:05
  • I was able to get it to compile in GCC by adding `void` after `friend` (`friend void T::T(const T&);`). I was then able to access one of `Test`'s private members from `BeMyFriend`'s constructor. However, this did not work when I tried it in clang. – osdev0 Sep 24 '18 at 01:35
  • @osdev0 Hey, very cool. I tested it as well and got it to work fine. If you make an answer for this question, I'll accept it. Even if it isn't a solution for all compilers, it still works for GCC (which is what I'm using). – irfna Oct 04 '18 at 21:35

4 Answers4

0

Somehow I think T::T(const T&) isn't well parsed by the compiler because it doesn't consider the namespace T:: before the member constructor T() as a reference to some external class, obviously seen in error console ISO C++ forbids declaration of ‘T’ with no type and prototype for ‘BeMyFriend::BeMyFriend(const BeMyFriend&)’ does not match any in class ‘BeMyFriend’ where the compiler is blatantly trying to get any definition or declaration exported from outside the class, thus T:: should be forced by the user to get introduced to the compiler referenced to the befriended class like so T&:: this is sufficient to remove ambiguity.

You can check here that the instantiator "works" perfectly and the value is properly befriended.

If, neverthless, you see in this example the error shown std::__cxx11::string Test<BeMyFriend>::mything’ is private within this context areyoumyfriend.mything; depicts the status of violation access to a private value, this is because the member function is not befriended to the host class simply.

Abr001am
  • 571
  • 6
  • 19
0

I think the C++ standard prohibits what you are trying to do. Maybe it's a defect/oversight; maybe it was intentional. I'm not sure, so I'll just lay out the pieces I found in the standard (it's safe to skim over the section numbers unless/until someone is checking my analysis):

  1. In 6.4.3.1.2, there is a description of when something is considered to name a constructor. This involves the "injected-class-name" which (according to 12.2) means the name of the class inserted into the class' scope. In your context, I read these sections as saying that to name the constructor, you need T:: followed by the class-name of T. You have T::T, which works if T is considered the class-name of T. Let's see how this plays out.

  2. In 15.1.1, it is stated that your friend declaration would need to name a constructor. It goes on to say that the class-name shall not be a typedef-name. So we should be fine as long as T is not a typedef-name.

  3. In 17.1.3, it is stated that in your class Test, the identifier T is a typedef-name. Uh-oh.

So it's kind of weird. If I'm reading things correctly, you would need to use friend T::BeMyFriend to name the constructor, which of course only works in this one particular example. For other template parameters, this would look for a member function named "BeMyFriend". Not what you are looking for.

(You might notice that in the examples of constructors declared as friends, the class name being used is always the name used when defining the class. In that situation, there is no typedef'ing going on, so this issue does not arise.)

Solution? I think you need to make the class T a friend, make a static function in T a friend and call that from the constructor, or find a (better?) way to do what you want to do without using friends. There is a warning flag waving in the back of my mind when I see a template parameter made a friend -- it's legal, but often goes against the principles of encapsulation.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
0

Constructors are very special methods. They have no return type (not even void) and no real name. AFAIK that just cannot be used in friend declaration.

A possible workaround is to build a copy factory function in class BeMyFriend:

class BeMyFriend
{
public:
 BeMyFriend& operator=(const BeMyFriend& rhs) = default;
 BeMyFriend(const BeMyFriend& rhs) = default;
 static BeMyFriend makeCopy(const BeMyFriend& rhs) {
    BeMyFriend tmp(rhs);
    return tmp;
 }
};

template<class T>
class Test
{
 friend T& T::operator=(const T&); //Works fine, no error
 //friend T::T(const T&); //error: prototype for 'BeMyFriend::BeMyFriend(const BeMyFriend&)' does not match any in class 'BeMyFriend'
 friend T T::makeCopy(const T&);
};

int main()
{
 Test<BeMyFriend> hmm;

 return 0;
}

This does not really answer your question because a default copy construction would not be friend, but in the followin code:

BeMyFriend foo;
BeMyFriend bar = BeMyFriend::makeCopy(foo);

you get a friend copy contruction inside makeCopy, and next one is likely to be elided.

Anyway I cannot really imagine a real use case for friending only a specific constructor from a class and not the whode class...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

I was able to get it to compile in GCC by adding void after friend:

friend void T::T(const T&);

I was then able to access one of Test's private members from BeMyFriend's constructor. However, note that this is compiler specific. I tried it in clang, and it did not work.

osdev0
  • 280
  • 4
  • 7