Home Dashboard Directory Help
Search

warning C4717 'recursive on all control paths error' erroneous by eldiener


Status: 

Closed
 as Won't Fix Help for as Won't Fix


12
1
Sign in
to vote
Type: Bug
ID: 522094
Opened: 12/25/2009 6:28:48 AM
Access Restriction: Public
0
Workaround(s)
view
7
User(s) can reproduce this bug

Description

Compiling the code below:

#include "StdAfx.h"

struct XX {};
struct YY {};

template <class T>
struct IInterface
{
virtual T InterfaceFunction() const = 0;
};

template <class T,
         class G = YY
         >
class AClassTemplate;

template <class T>
class AClassTemplate<T,XX> :
public IInterface<T>
{
private:
T data;
public:
T InterfaceFunction() const
    {
    return(data);
    }
AClassTemplate() {}
explicit AClassTemplate(T arg) {}
template<class U> explicit AClassTemplate(U arg) {}
};

template <class T>
struct AClassTemplate<T,YY> :
AClassTemplate<T,XX>
{
AClassTemplate() {}
explicit AClassTemplate(T arg) :
    AClassTemplate<T,XX>(arg) {}
};

struct PAutoClass
{
AClassTemplate<double> AVariable;
PAutoClass():AVariable(56.43) {}
};

struct PAutoX
{
AClassTemplate<PAutoClass> ClassProperty;
PAutoX() {}
};

void TestAClassTemplate()
{
PAutoX x;
}

In C++ I get a 'warning C4717: 'AClassTemplate<double,YY>::AClassTemplate<double,YY>' : recursive on all control paths, function will cause runtime stack overflow'.

I can not see why this warning is occuring. The compiler seems very confused. There is no recursion occuring which will cause a runtime stack overflow.

If the line 'template<class U> explicit AClassTemplate(U arg) {}' is commented out, no warning occurs.

If the inheritance from public IInterface<T> is removed, no warning occurs.
Details
Sign in to post a comment.
Posted by Microsoft on 1/5/2010 at 3:03 PM
@eldiener:

We've re-reviewed the bug report and, as you pointed out, this is a duplicate of issue 423737 and is indeed a bug. We apologize for claiming otherwise. The problem is caused by the compiler failing to exclude non-copy constructors when in the context of initializing base objects in a compiler-generated copy constructor and instead using overload resolution in the context of direct-initialization (which causes the templated constructor to be specialized and added to the overload resolution set, further resulting in the infinite recursion).

The quote: "The compiler is required to generate a copy constructor..." had a typo - it should have said the the base *copy* constructor would be called from the generated copy constructor and indeed the compiler generates such a call. The subtlety occurs in the form of the call, i.e. the way in which the compiler is initializing the base-object which results in the above described behavior. The second part of the quote is correct in that if a user defined constructor omits a base-initializer list, the base is default initialized but it's not really relevant to the problem. Sorry about any confusion caused by this but this was meant as a response to your query:

"In my example there is no derived class user-generated copy constructor so I do not see how the recursion as explained in your example can occur in my code."

Unfortunately, we're unable to fix this for the upcoming release due to its proximity. We shall however consider it for a future release.

Tanveer Gani
Visual C++

Posted by Johannes Schaub [litb] on 1/5/2010 at 11:52 AM
It can be easier seen why the compiler has to initializer the base using its base class type when doing the dance with members:

struct A { }; struct B { A a; };

The Standard asks us to copy the sub-objects over. That means for "a" above you take "other.a" as source, and for a base class subobject you take the corresponding base class type and object, not the derived class. This is where msvc is wrong, i think.

GCC is implementing DR#535 retroavtively too, and choses not only among copy constructors - I don't think msvc is wrong on that one.
Posted by Leigh Johnston on 1/5/2010 at 11:35 AM
Related:

struct A { A(); A(A const&); };
struct B : virtual private A { };
struct C : B { };

C c1;
C c2 = c1;

This fails to compile in VS2008 but compiles fine in g++ and comeau, the type passed to A ctor should be A& not C&.
Posted by Leigh Johnston on 1/4/2010 at 2:41 PM
I have been informed that http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#535 may have some bearing on this.
Posted by Leigh Johnston on 1/4/2010 at 1:49 PM
It gets worse:

////////////////////////////////////////////////////
class Parent;

class Child
{
public:
Child() {}
Child(const Child&) { std::cout << "A"; }
explicit Child(const Parent&);
};

class Parent : public Child {};

Child::Child(const Parent&) { std::cout << "B"; }

int main()
{
Parent parent1;
Parent parent2 = parent1; // warning C4717
}
////////////////////////////////////////////////////

---

On VC++ this program outputs "B", on g++ is outputs "A".
Posted by Niels Dekker on 1/4/2010 at 8:00 AM
Thanks, Leigh. Note that the runtime stack overflow may also occur when there aren't any templates. For example, the following gave me a stack overflow as well when I used Visual C++:

////////////////////////////////////////////////////
class Parent;

class Child
{
public:
Child() {}
Child(const Child&) {}
explicit Child(Parent);
};

class Parent : public Child {};

Child::Child(Parent) {}

int main()
{
Parent parent1;
Parent parent2 = parent1; // warning C4717
}
////////////////////////////////////////////////////

Interestingly, the stack overflow was gone when I removed the user-defined copy-constructor of the base class, Child. I think this is a major compiler bug.
Posted by Leigh Johnston on 1/3/2010 at 2:49 PM
From "The C++ Programming Language 3rd Ed." (not the standard I know): "a template constructor is never used to generate a copy constructor, so without the explictly declared copy constructor, a default copy constructor would have been generated." 13.6.2 Member Templates

Go fix! :)
Posted by eldiener on 1/3/2010 at 2:30 PM
This is essentially the same VC++ bug as 423737 reported back in March of 2009, which you refused to fix, but at least managed to admit was a bug.

This is what happens when Microsoft refuses to fix obvious bugs in VC++. Not only do you look foolish denying a bug report for something you have already admitted is a bug, as well as wasting your time dealing with a bug report which has already been reported to you in another form, but you also waste the time of programmers who have to encounter the same bug, find a workaround for it, and report it back to you and respond to your false denials. And you are not even ashamed of yourselves for slowing down VC++ programmers and programming in this way at all.
Posted by eldiener on 1/3/2010 at 12:07 PM
Please see the discussion at http://groups.google.com/group/comp.std.c++/browse_thread/thread/cbbe84048c24fc58# showing this is a Microsoft bug.
Posted by Niels Dekker on 1/2/2010 at 8:48 AM
I'm even more concerned about potential incorrect runtime behavior. The following simple little program gets me warning C4717 *and* a stack overflow at runtime (in debug mode). I think that's incorrect behavior. I'm using VC 2008 SP1.

//////////////////////////////////////////////////
class Bar
{
public:
    virtual ~Bar() {}
protected:
    Bar() {}
    template<typename U> explicit Bar(U) {}
};

class Foo : public Bar {};

int main()
{
    Foo foo1;
    Foo foo2 = foo1; // warning C4717
}
//////////////////////////////////////////////////
Posted by Niels Dekker on 1/2/2010 at 4:02 AM
Reproduced with VC++ 2008 SP 1. Slightly simplified the code originally posted here by eldiener (removed #include "StdAfx.h"), still got the warning. Added a main() function, calling TestAClassTemplate(); ran the program, experienced no stack overflow. So indeed, warning C4717 does not seem applicable here.

BTW Here's a link to the comp.std.c++ thread on this issue: "Templated constructor or copy constructor", http://groups.google.com/group/comp.std.c++/browse_frm/thread/cbbe84048c24fc58/
Posted by eldiener on 12/31/2009 at 9:01 PM
In the original code I posted there was no user-defined copy constructor in the derived class. In the example you posted, you give a user-defined copy constructor in the derived class, which does not call the copy constructor of the base class, and then you give that irrelevant example as a means of justifying the original recursion. I well understand that if I have:

struct X
{
X() {}
template<class U> X(U arg) {}
};

struct Y : X
{
Y() {}
Y(const Y & arg) : X(arg) {}
};

Y a;
Y b(a);

that recursion will occur, but if I change the derived class's copy constructor to:

Y(const Y & arg) : X(static_cast<const X &>(arg)) {}

no recursion will occur.

But now just leave out the derived class's copy constructor altogether, such that the class is:

struct Y : X
{
Y() {}
};

and I do not see why that recursion should occur. This is the simplified equivalent of my origina post, where no derived class user-defined copy constructor exists.

Is your argument that a compiler-defined derived class copy constructor is essentially created as:

Y(const Y & arg) : X(arg) {}

as opposed to the what I believe is the correct

Y(const Y & arg) : X(static_cast<const X &>(arg)) {}

?
Posted by eldiener on 12/31/2009 at 6:38 PM
Referring to your quote:

"The compiler is required to generate a copy constructor for you if you don't supply one. Such a copy constructor will also call the default base class constructor (and give an error if one does not exist). The base class default constructor will also be called if you define the copy constructor but don't explicitly provide a call in your base-initializer list for the base class constructor."

I am assuming by "default base class constructor" above you mean the constructor which takes no arguments.

The generated copy constructor in the derived class will call the copy constructor of the base class, or the generated copy constructor of the base class if there is no user-defined base class copy constructor. It does not call the default base class constructor, whether it exists or not. If it did the latter, then no generated derived class copy constructor would work correctly since the base class's data would never be copied correctly before the derived class's data gets copied. After all a generated derived class copy constructor will only be copying the data in its own class, so how can the data in a base class be copied if the base class's copy constructor is not also being called first ?

I have been told on comp.std.c++, as I had originally surmised, that the generated derived class copy constructor call does not call the templated constructor as a better match than the generated base class copy constructor. So recursion should not occur if I do not myself supply a derived class copy constructor when the base class has a templated constructor, as in my original and your example. If you believe that this is wrong could you please revisit this issue among your C++ compiler gurus to ascertain that the C++ standard does indeed let the templated base class constructor have preference over the copy constructor when the compiler is generating the derived class copy constructor itself. I will continue to pursue this on comp.std.c++ for further clarification of what I have been told.

If one codes a user-defined derived class copy constructor, then of course it will call whatever the constructor one specifies in the base class, or it will call the base class's default constructor if no base class constructor is specified.
Posted by Microsoft on 12/31/2009 at 3:10 PM
@eldiener:
Yes, there is indeed an infinite recursion due to the explained reasons, i.e., the constructor generated from the template is a better match than the other available constructors (if any). The compiler has no choice but to follow the language as defined but unfortunately this results in infinite recursion. Simply put, the templated constructor in the base class is the root of the problem: remove it and your problem is solved.

The compiler is required to generate a copy constructor for you if you don't supply one. Such a copy constructor will also call the default base class constructor (and give an error if one does not exist). The base class default constructor will also be called if you define the copy constructor but don't explicitly provide a call in your base-initializer list for the base class constructor.

@Adam C Merz:
The back-end is generating the warning about recursion on all paths and it does thorough flow analysis to generate this warning. However, the warning is limited in that it only names the constructor and doesn't give a full prototype, which tends to confuse the issue when there are overloads present. You can use the /FAsc option to generate the assembly listing, decode the decorated names using the undname.exe utility and inspect it for the recursion. In fact it's easily spotted in the original repro code (marked with "*** RECURSIVE CALL ***" below):

public: __thiscall AClassTemplate<double,struct YY>::AClassTemplate<double,struct YY>(struct AClassTemplate<double,struct YY> const &) PROC ; AClassTemplate<double,YY>::AClassTemplate<double,YY>, COMDAT
; _this$ = ecx
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 83 ec 08 sub esp, 8
00006 89 4d f8 mov DWORD PTR _this$[ebp], ecx
00009 83 ec 10 sub esp, 16 ; 00000010H
0000c 8b cc mov ecx, esp
0000e 8b 45 08 mov eax, DWORD PTR ___that$[ebp]
00011 50 push eax
00012 e8 00 00 00 00 call public: __thiscall AClassTemplate<double,struct YY>::AClassTemplate<double,struct YY>(struct AClassTemplate<double,struct YY> const &) *** RECURSIVE CALL ***
00017 8b 4d f8 mov ecx, DWORD PTR _this$[ebp]
0001a e8 00 00 00 00 call public: __thiscall AClassTemplate<double,struct XX>::AClassTemplate<double,struct XX><struct AClassTemplate<double,struct YY> >(struct AClassTemplate<double,struct YY>) ; AClassTemplate<double,XX>::AClassTemplate<double,XX><AClassTemplate<double,YY> >
0001f 8b 4d f8 mov ecx, DWORD PTR _this$[ebp]
00022 c7 01 00 00 00
00 mov DWORD PTR [ecx], OFFSET const AClassTemplate<double,struct YY>::`vftable'
00028 8b 45 f8 mov eax, DWORD PTR _this$[ebp]
0002b 8b e5 mov esp, ebp
0002d 5d pop ebp
0002e c2 04 00 ret 4
public: __thiscall AClassTemplate<double,struct YY>::AClassTemplate<double,struct YY>(struct AClassTemplate<double,struct YY> const &) ENDP ; AClassTemplate<double,YY>::AClassTemplate<double,YY>
_TEXT ENDS

If the code isn't crashing it may be that it's never called. You can insert a breakpoint (unfortunately at the assembly level since this is a compiler generated constructor) to make sure.

Tanveer Gani
Visual C++


Posted by ildjarn on 12/28/2009 at 4:39 PM
Except that there is, in fact, *no* runtime stack overflow when the code is executed; it does not fail when run...
Posted by eldiener on 12/28/2009 at 3:01 PM
Are you trying to tell me that in my original example the derived class's compiler generated copy constructor is going to call to my base class's templated constructor rather than to my base class's compiler generated copy constructor ? That does not seem like it could ever be correct.

In my example there is no derived class user-generated copy constructor so I do not see how the recursion as explained in your example can occur in my code.
Posted by eldiener on 12/28/2009 at 2:40 PM
Then what is the way to avoid a recursive call in this situation ? Clearly one should be allowed to call a base class's templated constructor from a derived class.
Posted by Microsoft on 12/28/2009 at 1:47 PM
Thank you for reporting this issue to Microsoft. The compiler warning is correct and you do indeed have a recursive copy constructor that will cause the program to fail when run. Here's a shortened example:

template<typename T> struct B {
template<class U> explicit B(U arg);
};
struct D : B<int> {
D();
D(const D& other) : B<int>(other) {}
};
int main()
{
D d1;
D d2 = d1;
}

Note the copy-ctor for D, in particular the call to B<int>::B(other). To resolve this call, the templated constructor of B is specialized with U=D and overload resolution can select this since it matches exactly. However to _call_ this generated constructor with "other", the copy constructor of "D" ahs to be invoked to perform the "other" -> "arg" copy intialization. This is the recursive call that the compiler is warning about.

Other compilers may not warn here as a complete flow analysis is required. Typically this is done by back-ends and online front-ends like the one provided by Comeau might not do this.

Hope this helps.

Tanveer Gani
Visual C++
Posted by Microsoft on 12/27/2009 at 7:25 PM
Thanks for reporting the issue.
We were able to reproduce the issue with the detail steps you provided. We are escalating this bug to the product unit who works on that specific feature area. The product team will review this issue and make a decision on whether they will fix it or not for the next release.
Posted by ildjarn on 12/25/2009 at 3:24 PM
Reproduced with VC++ 2010 beta 2.
Sign in to post a workaround.