2

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?

KFox
  • 1,166
  • 3
  • 10
  • 35
  • 2
    I think the problem is that, within the definition of `Bar`, plain name `Bar` refers to *this* specialization - that is, `Bar` - rather than the template as a whole. One workaround: add `template using Bar2 = Bar;` at the top of `Bar` definition, and use `Bar2` in place of `Bar` where you need to refer to the unspecialized template. – Igor Tandetnik Jan 17 '16 at 02:07
  • This seems recursive. Bar has a Foo member, but that Foo is templated such that it has the exact same Bar member as the class it is a part of. So Bar contains itself, which isn't allowed. The particular error is caused by trying to use Bar as a template template parameter during its definition, without a forward declaration, I think. – Nir Friedman Jan 17 '16 at 02:09

2 Answers2

2

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.

1

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.

Community
  • 1
  • 1
KFox
  • 1,166
  • 3
  • 10
  • 35
  • Some of the workarounds you've posted are honestly quite weird. I think this is a sort of X-Y situation, you may be better off trying to tell people what you are trying to do. I mean, are you trying to write a recursive data structure? It's unusual, and relatively rarely a good idea (in C++) for data structures to have pointers to other instances of their class. – Nir Friedman Jan 18 '16 at 00:22
  • I'm just sharing the things that I've found to make this class structure work. I am passing data around between various modules (represented here by the `Bar` class. They have to know which other modules they are connected to, and the connections are described by the `Foo` class. Is there a better way to describe modules and their connections using C++? – KFox Jan 18 '16 at 23:51