views:

136

answers:

4

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?

A: 

My guess you;re way into quirks territory here with friend templated constructors. This compiles and runs fine on VS2010 but it produces a stack overflow when default constructor of A calls the default constructor of B which then instantiates A again.

Igor Zevaka
Yeah, definitely don't wanna try and instantiate of those classes I wrote, hehe. Notice the explicit `int main() { return 0; }` I tacked on there, ie "dont try to do anything else" :-)
Kyle
+4  A: 

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.

James McNellis
I posted a defect report to Microsoft Connect: https://connect.microsoft.com/VisualStudio/feedback/details/558796/. I could only reproduce the issue when the class template specialization is a base class of the class attempting to befriend the constructor. Once you remove the inheritance from the code, Visual C++ seems to handle the friend declaration correctly. If you can find a simpler test case, feel free to post it as a comment to that defect report.
James McNellis
Curiously, they left the weird rule in clause 5.1.1/6 (FCD) which says "Where class-name :: class-name is used, and the two class-names refer to the same class, this notation names the constructor (12.1).". This rule is not only useless in the presence of 3.4.3.1/2 and contradicts it (`B<int>::B<int>` would name any non-template constructors). I was in the impression that issue #147 was supposed to remove that weird rule. I believe that clause 5 (which is about expressions) is the wrong place for such rules anyway (and the right one is 3.4).
Johannes Schaub - litb
@Johannes: I think §12.1/1 (C++03 and FCD) has the same issue: "...the constructor’s class name followed by a parameter list is used to declare or define the constructor."
James McNellis
+1  A: 

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.

Owen S.
True, it doesn't look like `A` should inherit from `B` and that would solve the infinite recursion problem.
Ben Voigt
+1  A: 

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.

Ben Voigt
Unfortunately, no, as the constructor cannot be named that way. It would have to be `friend Base::B()`, which is correct but which Visual C++ does not accept, or `friend Base::B<int>()`, which is incorrect but which Visual C++ does accept.
James McNellis
@James: In your opinion, does paragraph 3 of 12.1 refer only to the introducing (in class) declaration like paragraph 1, or does it also cover `friend` and `using` declarations.
Ben Voigt
@Ben: I don't think that citation of §3.4.3.1/2 is from the FCD--in the FCD, the second part starting at "or if the name specified..." is prefaced with "in a using-declaration that is a member-declaration," so that doesn't apply here. My current thought is that §12.1/3 applies any time that the constructor is declared, including in a friend declaration. I agree that the way it is phrased it forbids `friend Base::B();`. I am not sure that was the intent, though. I am trying to find the proposal or defect that resulted in that sentence getting added, but haven't had any luck.
James McNellis
Note also the comment by Johannes in response to my answer, that §5.1.1/6 (and I think §12.1/1) directly contradicts §3.4.3.1/2. I'm going to ask about this on comp.std.c++ when I get some free time this afternoon.
James McNellis
@James, I think you're right about it not being the FCD. Finding the FCD is a bit difficult these days, I think I found Herb Sutter blogging about the FCD being out and linking to a long list of papers, and trying to pick the draft standard out of the list. Apparently I got the wrong version. I think that `friend` declarations ought to be the same as `using` declarations, but not sure if they are still adjusting things which don't break existing code.
Ben Voigt