Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to befriend a templated class's constructor?

Why does

class A;
template<typename T> class B
{
private: 
    A* a;

public:  
    B();
};


class A : public B<int>
{
private:    
    friend B<int>::B<int>();
    int x;
};


template<typename T>
B<T>::B()
{
    a = new A;
    a->x = 5;
}

int main() { return 0; }

result in

../src/main.cpp:15: error: invalid use of constructor as a template
../src/main.cpp:15: note: use ‘B::B’ instead of ‘B::class B’ to name the constructor in a qualified name

yet changing friend B<int>::B<int>() to friend B<int>::B() results in

../src/main.cpp:15: error: no ‘void B::B()’ member function declared in class ‘B’

while removing the template completely

class A;
class B
{
private:
    A* a;

public:
    B();
};


class A : public B
{
private:
    friend B::B();
    int x;
};


B::B()
{
    a = new A;
    a->x = 5;
}

int main() { return 0; }

compiles and executes just fine -- despite my IDE saying friend B::B() is invalid syntax?

like image 711
Kyle Avatar asked May 12 '10 23:05

Kyle


3 Answers

Per the resolution to CWG defect 147 (the resolution was incorporated into C++03), the correct way to name a nontemplate constructor of a class template specialization is:

B<int>::B();

and not

B<int>::B<int>();

If the latter were allowed, there is an ambiguity when you have a constructor template specialization of a class template specialization: would the second <int> be for the class template or the constructor template? (see the defect report linked above for further information about that)

So, the correct way to declare the constructor of a class template specialization as a friend is:

friend B<int>::B();

Comeau 4.3.10.1 and Intel C++ 11.1 both accept that form. Neither Visual C++ 2008 nor Visual C++ 2010 accept that form, but both accept the (incorrect) form friend B<int>::B<int>(); (I will file a defect report on Microsoft Connect).

gcc does not accept either form prior to version 4.5. Bug 5023 was reported against gcc 3.0.2, but the requested resolution in the bug report was the invalid form. It appears the resolution to bug 9050 also resolves this issue and gcc 4.5 accepts the correct form. Georg Fritzsche verified this in a comment to the question.

like image 196
James McNellis Avatar answered Oct 05 '22 12:10

James McNellis


And the reason your IDE shows friend B::B() as invalid syntax in the latter case? An IDE error.

A workaround I found in gcc for the template case if you can't upgrade is to move the implementation of B() to a member function, void B::init(), and grant friendship to that instead. I'll bet this shuts up your IDE too.

Even if you get this to compile, though, you'll run into the stack overflow problem as soon as you try to instantiate a B.

like image 27
Owen S. Avatar answered Oct 05 '22 12:10

Owen S.


Doesn't a typedef help? e.g.

class A : public B<int>
{
    typedef B<int> Base;   
    friend Base::Base();
    int x;
};

EDIT: The Final Committee Draft for C++0x includes the following language in section 3.4.3.1 [class.qual]:

In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C: if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9), or if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id’s template-name in the last component of the nested-name-specifier, the name is instead considered to name the constructor of class C.

Sounds like the name (Base) specified after the nested-name-specifier (Base::) is the same as the identifier in the last component of the nested-name-specifier, so this code does name a constructor.

But I can't reconcile this with section 12.1 [class.ctor]:

Because constructors do not have names, they are never found during name lookup

Oh really? How's that language in 3.4.3.1 work again?

A typedef-name shall not be used as the class-name in the declarator-id for a constructor declaration.

This seems pretty clear, except that section 12.1 appears to discuss only the introducing declaration of a constructor as paragraph 1 excludes the nested-name-specifier, friend, and using. If it does apply to friend declarations, it also appears to forbid friend Base::B();

A special declarator syntax using an optional sequence of function-specifiers (7.1.2) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.

Those function-specifiers are inline, virtual, explicit, and constructors can't be virtual anyway.

like image 28
Ben Voigt Avatar answered Oct 05 '22 13:10

Ben Voigt