I am using templates in my C++ project, and I'm having an issue when using a templated type as a template template-parameter. I think the best way to describe it is to give an example that produces the error:
template <template<class> class P, typename T>
class Foo {
P<T> baz;
};
template <class T>
class Bar {
Foo<Bar, T> memberFoo;
void makeFoo() {
Foo<Bar, T>* f = new Foo<Bar, T>();
}
};
Foo<Bar, int> globalFoo;
The declaration of globalFoo causes no error, but the declarations of memberFoo and f cause the compiler error:
error: template argument for template template parameter must be a class template or type alias template
The error only occurs when using Bar as a template parameter inside of the declaration of the Bar class, but occurs using both clang and g++. This seems like something that would be documented somewhere, but googling yields no SO questions or other documentation.
Is this use of templates simply not legal in C++, or am I misunderstanding something about how to define and use templates? If this design architecture is not allowed by the C++11 standard, what is a workaround I can use?
The issue is that an incomplete type is yielded at time of instantiation. Clang and G++ yield different errors because Clang (apparently) doesn't implement a C++11 rule that the injected class name can refer to the class template itself when used as a template template parameter (Igor's suggestion doesn't work btw.) Changing Bar to ::Bar fixes this error, which makes Clang point out the incomplete error like G++ does. Changing baz to P<T>* allows it to compile.
N.B. Even though it compiles, it probably is undefined behavior. I suggest redesigning your classes.
Based on @Igor 's comment, I have figured out a couple of workarounds for this issue. Are based on the fact that referencing Bar within its declaration refers to this specialization of Bar (to quote @Igor).
Note that all of these workarounds rely on the fact that baz can be declared as a pointer. baz not being a pointer causes a recursion issue that was mentioned by @Nir in the comments and leads to the error:
field has incomplete type 'Foo<Bar<int>, int>'
Workaround 1
Add a forward declaration of Bar and create a template alias Bar2. For this to compile, baz must be a pointer:
template <template <typename> class P, typename T>
class Foo {
P<T>* baz;
};
template<typename T> class Bar;
template <typename U> using Bar2 = Bar<U>;
template <class T>
class Bar {
Foo<Bar2, T> memberFoo;
void makeFoo() {
Foo<Bar2, T>* f = new Foo<Bar2, T>();
}
};
Foo<Bar, int> globalFoo;
The forward declaration and use of a template alias forces the compiler to use the unspecialized version of Bar when defining memberFoo and f, whereas it defaults to using the unspecialized version in the definition of globalFoo.
Workaround 2
This workaround is based on @user5800314's answer. I feel no need to re-state his workaround, but I do feel that it is worth noting the reason why it works.
I have read a similar SO question about injected class names and C++11, but the important difference here is that my code does not compile on g++, whereas theirs does. I do not believe that the issue is a lack of implementation of injected class names. I believe that this workaround fixes the compilation error because using ::Bar instead of Bar again forces the compiler to access the global (unspecialized) version of Bar instead of accessing the local (specialized) version of Bar.
Workaround 3
Specify Foo as having a class (or typename) template-parameter instead of a template template-parameter, and be explicit about which specialization is being used whenever using the Foo template. This also requires that baz is a pointer, and that it does not use a template type:
template <class P, typename T>
class Foo {
P* baz;
};
template <class T>
class Bar {
Foo<Bar, T> memberFoo;
void makeFoo() {
Foo<Bar, T>* f = new Foo<Bar, T>();
}
};
Foo<Bar<int>, int> globalFoo;
This workaround resolves the of potential confusion of template template-parameters by requiring that a specific class is provided to the Foo template. This workaround may not be usable in some cases, but may be an elegant solution in others. In my case, for instance, I will not need to instantiate an instance of Foo from outside of Bar, so this is a very nice way of getting around the compile error.
P.S. I really would like to credit @user5800314, since his workaround does work, however I provide a different explanation here, and since the explanation I provide here is what I believe is correct, I don't feel that I can mark @user5800314 's answer as accepted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With