Bug (?) in pgCC7.07: mangled name contains file-local name

Here’s a problem that I encountered in real life; reduced
to its minimal form, it does look a bit esoteric…

Below is a set of files: test.h, declaring two templated classes A and B; test_impl.h, defining a constructor for B; test1.cpp and test2.cpp, explicitly instantiating B<1> and B<2>; and test.cpp,
defining main() and making use of the constructors B<1>::B and B<2>::B.

Compilation with

> pgCC -A -V --remarks -c test.cpp test1.cpp test2.cpp

pgCC 7.0-7 64-bit target on x86-64 Linux 
Copyright 1989-2000, The Portland Group, Inc.  All Rights Reserved.
Copyright 2000-2007, STMicroelectronics, Inc.  All Rights Reserved.

goes through without any messages.

Linking results in unresolved symbols:

> pgCC  test.o test1.o test2.o
test.o(.text+0x14): In function `main':
: undefined reference to `B<>::__ct(A<> *)'
test.o(.text+0x1f): In function `main':
: undefined reference to `B<>::__ct(A<> *)'

This seems strange, as these functions clearly should
be generated in test1.cpp and test2.cpp.

Closer inspection shows that the mangled names for the
unresolved symbols in test.o and the weak generated
symbols in test1.o, test2.o differ:

> nm *.o 

                 U __ct__17B__tm__9_XCiL_1_1FP48A__tm__39_XOcsi1OcsQ2_13B__tm__5_XZ1Z4__E31Z1ZOO
                 U __ct__17B__tm__9_XCiL_1_2FP48A__tm__39_XOcsi1OcsQ2_13B__tm__5_XZ1Z4__E31Z1ZOO

0000000000000000 W __ct__17B__tm__9_XCiL_1_1FP48A__tm__39_XOcsi1OcsQ2_13B__tm__5_XZ1Z4__E21Z1ZOO
0000000000000000 t pgCC_compiled.

0000000000000000 W __ct__17B__tm__9_XCiL_1_2FP48A__tm__39_XOcsi1OcsQ2_13B__tm__5_XZ1Z4__E21Z1ZOO

which can be traced back to the fact that names generated ad hoc and local to their translation unit
(__E2 and __E3) are part of these mangled names:

> nm test.o test1.o test2.o|pgdecode

                 U B<N1>::B<(int)1>(A<((int)((B<N1>::__E3)N1))> *) [with N1=(int)1]
                 U B<N1>::B<(int)2>(A<((int)((B<N1>::__E3)N1))> *) [with N1=(int)2]

0000000000000000 W B<N1>::B<(int)1>(A<((int)((B<N1>::__E2)N1))> *) [with N1=(int)1]

0000000000000000 W B<N1>::B<(int)2>(A<((int)((B<N1>::__E2)N1))> *) [with N1=(int)2]

The code compiles and links flawlessly under all versions of Gnu, Intel, and Pathscale C++ available to me. Also, the C++ standard does not give any
restrictions on the template-parameter dependence of integral template parameters, which leads me to believe that the code is perfectly legal. The standard also states (14.4.1) that templates whose integral parameters have the same value should be considered type-equivalent. I would assume that replacing “(int)(B::__E2)N1” with the value of N1 in the mangled name would be the standard-conformant thing to do. However, I am not a language lawyer, so any elucidation or correction would be appreciated. Also, I am of course aware that naming the anonymous
enum will get rid of the problem.


Source files:

// file test.h
template<int> struct A {};

template<int N> struct B {
  enum { n=N };
  B(A<n> *p=0);

// file test_impl.h
template<int N> 
B<N>::B(A<n> *) {}

//file test1.cpp
#include "test.h"
#include "test_impl.h"
template class B<1>;

// file test2.cpp
#include "test.h"
#include "test_impl.h"
template class B<2>;

// file test.cpp
#include "test.h"
int main() {
    B<2> b1;
    B<1> b2;        


Thank you for the small test case. We have reproduced the undefined reference here.

As you have discovered, the nameless enum is causing a slight difference in the mangled
template names. This is a side effect of our name mangling scheme, and the fact that
test_impl.h is not included by test.c. Add #include “test_impl.h”, and the code links fine.

The Ansi standard states that an implementation may require all template definitions to
be visable at their instantiation point. So while your code may compile on some compilers,
it is not strictly portable as a result of the missing #include “test_impl.h”
Regardless, this gnu incompatibility may well be fixed in an upcomming release, and has been assigned as TPR 4113

Right. The point, however, was to have the instantiation in
a library, as the compilation is very expensive (the original
class template is recursive and pulls in a series of functions
for a series of datatypes and N values).

The Ansi standard states that an implementation may require all template definitions to be visable at their instantiation point.

In that case, I stand corrected. I was pretty certain that the code
was well-formed. The only section of the ANSI standard I found applicable
states that (ISO14882:2003, 14.8.2)

A non-exported template
must be defined in every translation unit in which it is implicitly instantiated (14.7.1), unless the corresponding
specialization is explicitly instantiated (14.7.2) in some translation unit; no diagnostic is