Taylor
Taylor

Reputation: 139

Undefined reference to specialized template member

I have a class parameterized by a template template class with a static member function:

template <template <typename> class F>
struct A {
  static int foo();
};

This class has no default definition for foo, and must be specialized for different types.

I also have another class parameterized by a template template class with a nested template class:

template <template <typename> class F>
struct B {
  template <typename T>
  struct C {};
};

I want C to specialize A for any template template class F that specializes A already:

template <template <typename> class F>
struct A<B<F>::template C> {
  static int foo();
};

template <template <typename> class F>
int A<B<F>::template C>::foo() {
  return A<F>::foo() / 2;
}

So, if I have a class that specializes A:

template <typename T>
struct E {};

template <>
int A<E>::foo() {
  return 42;
}

I would expect to be able to use the specialization like this (and return 21):

int bar() {
  return A<B<E>::template C>::foo();
}

However, this fails to link - it cannot find the reference to A<B<E>::C>::foo().

(Note that all of this is in a single file - there is nothing weird happening with headers here)

It appears that the compiler is trying to use the primary template for A rather than the specialization, which means that foo is undefined. Why does it not use the specialization in this instance?

Full Example

template <template <typename> class F>
struct A {
  static int foo();
};

template <template <typename> class F>
struct B {
  template <typename T>
  struct C {};
};

template <template <typename> class F>
struct A<B<F>::template C> {
  static int foo();
};

template <template <typename> class F>
int A<B<F>::template C>::foo() {
  return A<F>::foo() / 2;
}

template <typename T>
struct E {};

template <>
int A<E>::foo() {
  return 42;
}

int bar() {
  // Link fails - error: undefined reference to 'A<B<E>::C>::foo()'
  return A<B<E>::template C>::foo();
}

Upvotes: 4

Views: 404

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275220

template<class T>
struct A {};

template<class T>
struct B {
  using type=T;
};

template<class T>
struct A<typename B<T>::type> {};

this is basically the same but with 1 fewer template layer.

This doesn't work either.

The problem is that B<T>::type or B<T>::template Z or whatever is, in the general case, an arbitrary compile-time function.

And in order to pattern match against it, we need to invert this arbitrary compile time function.

The standard says "compilers don't have to do that", which is one of the few sane things you can do here. It definitely says this for types; for templates, well, the standard's wording for template template parameters is often missing details, so I wouldn't be surprised if the wording was missing. But if it doesn't, it would be a bug in the standard.

In order to go from

template<class T>
struct A<typename B<T>::type> {};

to see if A<foo> matches it, it would have to test all types T to see which of them have a B<T>::type that equals foo.

This may not be what you intend to ask, but that is what you are asking for.

The same is true of your template example.

template <template <typename> class F>
struct A<B<F>::template C> {
  static int foo();
};

you are asking for the compiler to check every type F such that if you pass it to the arbitrary template B<> then evaluated ::C in it, does the template match what you are passing A.

First fun case:

template<class X>
struct C0 {};
template <template <typename> class F>
struct B {
  template <typename T>
  using C=C0<X>:
};

now, what is F in A<C0>? Every single F qualifies.

template<class X>
struct C0 {};
template <template <typename> class F, class=void>
struct B {
  template <typename T>
  using C=C0<X>:
};
template<class X>
struct C1 {};
template <template <typename> class F, class=void>
struct B<
  F,
  std::enable_if_t<
    proves_collatz_conjecture( F<int>::value )
  >
> {
  template <typename T>
  using C=C1<T>;
};

now to pattern mach A<C0> the compiler must produce the F such that F<int>::value is a compile-time type that when passed to proves_collatz_conjecture returns true at compile time.

That would be useful.


template specialization is pattern matching. In C++ you cannot pattern match against dependent types (and presumably templates) as neither types nor templates have identity beyond their value.

You cannot inspect the scope that a variable, type or template is defined in. So you cannot pattern match that either.

If you want do do what you want, the template C has to itself have a property you can inspect and test against.

Upvotes: 5

Related Questions