Reputation: 9388
I am facing a weird problem with VS2010. The following code fails to compile (while being perfectly accepted by gcc):
// Instantiate each type of a type array (which is a template<int>)
// void is taken as the end of the array
template<template<int> class A, int n = 0, typename obj_type = typename A<n>::type>
struct Instantiate : public Instantiate<A,n+1>
{
obj_type object;
Instantiate() : object() {}
};
template<template<int> class A, int n>
struct Instantiate<A,n,void>
{
};
// Does not compile
template<typename O>
struct test
{
template<int n, bool=true> struct array { typedef void type; };
template<int n> struct array2 { typedef typename array<n>::type type; };
template<bool u> struct array<0,u> { typedef int type; };
template<bool u> struct array<1,u> { typedef float type; };
template<bool u> struct array<2,u> { typedef bool type; };
Instantiate<array2> objects;
};
int main()
{
test<int> obj;
}
With the following error:
C1202: recursive type or function dependency context too complex
see instanciation of class template 'Instantiate<A,n>'
with
[
A=test<O>::array2,
n=1
]
(and so on, up to n=497...)
Making test
a non-template (ie, juste removing the line template<typename O>
) solves the error. However, despite it is possible in this simple example, it is not possible in my code (which is a lot more complex).
The questions are:
EDIT: - From jerry's answer, it seems it is a compiler bug... and it is fixed on VS2012. - The generated class hierarchy should be:
Instantiate<array2, 3, void> // end of recursion
^
|
Instantiate<array2, 2, bool>
^
|
Instantiate<array2, 1, float>
^
|
Instantiate<array2, 0, int>
Upvotes: 2
Views: 356
Reputation: 9388
While messing with rise4fun and jerry's workaround, I found that putting the whole definition of Instantiate
inside test
'solved' the problem (see jerry's answer for more information on VS2012CTP vs VS2012RTM):
template<typename O>
struct test
{
template<template<int> class A, int n = 0, typename obj_type = typename A<n>::type>
struct Instantiate : public Instantiate<A,n+1>
{
obj_type object;
Instantiate() : object() {}
};
template<template<int> class A, int n>
struct Instantiate<A,n,void>
{
};
template<int n, bool=true> struct array { typedef void type; };
template<int n> struct array2 { typedef typename array<n>::type type; };
template<bool u> struct array<0,u> { typedef int type; };
template<bool u> struct array<1,u> { typedef float type; };
template<bool u> struct array<2,u> { typedef bool type; };
Instantiate<array2> objects;
};
int main()
{
test<int> obj;
}
Which is perfect for my use case as it can be included in a macro already used by the user.
Upvotes: 0
Reputation: 2611
I can reproduce this in VS 2010 (with Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
) and I believe it is is a bug. Truth be told, I'm having a hard time wrapping my head around the scope issues involved here, but that seems to have no bearing on the problem at hand (as the base templates and specializations are nested together and VS is complaining about recursion level, not an undefined symbol). In any case, it's a very interesting example. I also see the same behavior in VS 2005 (with Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86
). It doesn't seem that Microsoft is still accepting bug reports on VS 2010 (judging by the Microsoft Connect Visual Studio page and feedback form). Hopefully someone with access to VS 2012 will be able to test your original code and file a bug report if the problem hasn't been fixed yet (which I think is likely, I see no indication that it's been brought to Microsoft's attention before).
To work around the issue, forward declare the class template that's causing the recursion:
// Forward declaration of problematic inherited class template
template<template<int> class A, int n = 0, typename obj_type = typename A<n>::type>
struct Instantiate;
template<template<int> class A, int n>
struct Instantiate<A,n,void>
{
};
// Now compiles!
template<typename O>
struct test
{
template<int n, bool=true> struct array { typedef void type; };
template<int n> struct array2 { typedef typename array<n>::type type; };
template<bool u> struct array<0,u> { typedef int type; };
template<bool u> struct array<1,u> { typedef float type; };
template<bool u> struct array<2,u> { typedef bool type; };
Instantiate<array2> objects;
};
// Instantiate each type of a type array (which is a template<int>)
// void is taken as the end of the array
template<template<int> class A, int n, typename obj_type>
struct Instantiate : public Instantiate<A,n+1>
{
obj_type object;
Instantiate() : object() {}
};
int main()
{
test<int> obj;
return 0;
}
Synxis, thanks for the rise4fun link, that's sure to come in handy later. The amazing thing is that while your original code works fine in VS2012 CTP
, it's still broken in VS2012 RTM
! See http://rise4fun.com/Vcpp/aRh. Why CTP
is the default option instead of RTM
is beyond me. This example is definitely a strange one.
Is there anything other than Instantiate
in your library header that is used by test
? If not, the user can explicitly forward declare the Instantiate
template define test
, and then include your header. Otherwise, you can create a header file that has the forward declaration in it along with the other things that are needed. Most likely you'll want to create special workaround headers for users affected by this problem. This certainly makes things very messy and error prone (mostly due to the default template arguments), but it will work:
myLib_workaround_forward_declaration.hpp:
#ifndef MYLIB_WORKAROUND_FORWARD_DECLARATION // or #pragma once if supported
#define MYLIB_WORKAROUND_FORWARD_DECLARATION
// amount of error checking is up to you
// eg, are workaround header files mixed with the regular ones
// or included in the wrong order
template<template<int> class A, int n = 0, typename obj_type = typename A<n>::type>
struct Instantiate;
#endif
myLib_workaround_implementation.hpp:
#ifndef MYLIB_WORKAROUND_IMPLEMENTATION // or #pragma once if supported
#define MYLIB_WORKAROUND_IMPLEMENTATION
// amount of error checking is up to you
// eg, are workaround header files mixed with the regular ones
// or included in the wrong order
template<template<int> class A, int n, typename obj_type>
struct Instantiate : public Instantiate<A,n+1>
{
obj_type object;
Instantiate() : object() {}
};
template<template<int> class A, int n>
struct Instantiate<A,n,void>
{
};
#endif
userFile.cpp:
// Forward declaration of problematic inherited class template
#include "myLib_workaround_forward_declaration.hpp"
// Now compiles!
template<typename O>
struct test
{
template<int n, bool=true> struct array { typedef void type; };
template<int n> struct array2 { typedef typename array<n>::type type; };
template<bool u> struct array<0,u> { typedef int type; };
template<bool u> struct array<1,u> { typedef float type; };
template<bool u> struct array<2,u> { typedef bool type; };
Instantiate<array2> objects;
};
#include "myLib_workaround_implementation.hpp"
int main()
{
test<int> obj;
return 0;
}
Upvotes: 1
Reputation: 18368
It's not a bug, it's compiler configuration. Compile-time stack is limited just like run-time stack. IIRC in VS the maximum depth of compile-time stack is 512. Might worth seeking in the documentation, maybe it's configurable, though not sure.
Upvotes: 0