cxx-ada-template-qualification

C++ Ada template-instance qualification

The C++ Ada spec dumper drops the generated package qualifier from references to concrete template instances, making specs with multiple instantiations ambiguous.

AcceptedApplies standaloneSince 1.2.0

Where it applies.

How each patchset treats this bundle on each GCC major
PatchsetGCC 13GCC 14GCC 15GCC 16
1.2.0 (latest)Patchedgcc-13-1413.2.0Patchedgcc-13-1414.2.0Patchedgcc-15-1615.3.0Patchedgcc-15-1616.2.0
1.1.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.2.0
1.0.1Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0
1.0.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0

Explanation.

The C++ -fdump-ada-spec mapper emits each concrete class-template instantiation in a nested Ada package, then adds a use clause for every such package. References to an instance outside its package are emitted with only the class name. Once two instantiations exist, those references are ambiguous and the generated Ada specification cannot compile.

For example, this C++ declaration creates two concrete instances and refers to them outside their generated packages:

extern template class Box<int>;
extern template class Box<double>;

using Int_Box = Box<int>;
template <typename T> using Alias_Box = Box<T>;
using Alias_Int_Box = Alias_Box<int>;
struct Holder { Box<int> item; };
Box<double> identity (Box<double> value);

The unpatched mapper loses the instance packages:

subtype Int_Box is Box;
subtype Alias_Int_Box is Alias_Box;
item : aliased Box;
function identity (value : Box) return Box;

Because both Box_int and Box_double are made directly visible, Box is ambiguous. The corrected output names the owning package at every external reference:

subtype Int_Box is Box_int.Box;
subtype Alias_Int_Box is Box_int.Box;
item : aliased Box_int.Box;
function identity
  (value : Box_double.Box) return Box_double.Box;

An alias-template instance is the subtle GCC 13 case. When that mapper emits a second package for the alias, it names the Ada type Alias_Box, but its constructor result falls back to the underlying C++ type name, which is ambiguous between the two used Box packages:

package Alias_Box_int is
   type Alias_Box is limited record
      -- ...
   end record;
   function New_Alias_Box return Box;
end Alias_Box_int;

The corrected constructor result uses the type declared in its own package, as required by Ada's CPP_Constructor rule:

function New_Alias_Box return Alias_Box;

The patch finds the nested package that the mapper already generates for each concrete template instance and qualifies external references with it. It tracks the current generated package, rather than only the C++ type identity, because Alias_Box<int> and Box<int> are the same C++ type but are emitted under different Ada names. Internal references use the current package's own type name, while external references use the concrete instance's qualified name. The patch covers typedefs, ordinary using aliases, alias-template instances, record fields, function parameters, and function results. References inside the instance's own package remain unqualified.

GCC 14 and later do not emit that duplicate alias package for this fixture. The regression therefore requires the exact self-owned constructor result whenever the package is present, without requiring a declaration that those versions intentionally omit.

The executable repository regression first asks g++ to generate the Ada specification, then compiles and runs an Ada program that depends on it. The unpatched test requires GNAT's ambiguous-name rejection; the patched test requires all five qualified external forms, validates any emitted alias constructor, and prints PASS C++ Ada template qualification at -O0 and -O2.

The defect was recovered from a local exploratory transcript and independently reproduced with the pinned GCC 13.2.0, 14.2.0, 15.3.0, 16.1.0, and 16.2.0 toolchains. patches/gcc-13-14.patch and patches/gcc-15-16.patch differ only where the mapper's pretty-printer parameter changed from buffer to pp.

Patch.

Variant gcc-13-14

Applies to 13.2.0, 14.2.0. Source flavors: linux, darwin_arm64.

+100 −2 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +65−2modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -1327 +1327 @@separate_class_package (tree decl)
13271327}
13281328
13291329static bool package_prefix = true;
1330Added line. static tree current_template = NULL_TREE;
1331Added line. static tree current_template_instance = NULL_TREE;
13301332
13311333/* Dump in BUFFER the name of an identifier NODE of type TYPE, following Ada
13321334 syntax. LIMITED_ACCESS indicates whether NODE can be accessed through a
@@ -1855 +1857 @@dump_template_types (pretty_printer *buffer, tree types, int spc)
18551857 }
18561858}
18571859
1860Added line. /* If NODE is a concrete template instance emitted in a nested package,
1861Added line. dump the type name owned by that package. */
1862Added line.
1863Added line. static bool
1864Added line. dump_template_type_name (pretty_printer *buffer, tree node, int spc)
1865Added line. {
1866Added line. if (!package_prefix || !cpp_check)
1867Added line. return false;
1868Added line.
1869Added line. if (current_template_instance
1870Added line. && TYPE_MAIN_VARIANT (node)
1871Added line. == TYPE_MAIN_VARIANT (current_template_instance))
1872Added line. {
1873Added line. package_prefix = false;
1874Added line. dump_ada_node (buffer, current_template_instance, current_template,
1875Added line. spc, false, true);
1876Added line. package_prefix = true;
1877Added line. return true;
1878Added line. }
1879Added line.
1880Added line. for (int i = 0; i < to_dump_count; i++)
1881Added line. {
1882Added line. tree tmpl = to_dump[i];
1883Added line.
1884Added line. if (!cpp_check (tmpl, IS_TEMPLATE))
1885Added line. continue;
1886Added line.
1887Added line. /* DECL_SIZE_UNIT is DECL_TEMPLATE_INSTANTIATIONS in this context. */
1888Added line. for (tree inst = DECL_SIZE_UNIT (tmpl);
1889Added line. inst && inst != error_mark_node;
1890Added line. inst = TREE_CHAIN (inst))
1891Added line. {
1892Added line. tree types = TREE_PURPOSE (inst);
1893Added line. tree instance = TREE_VALUE (inst);
1894Added line.
1895Added line. if (!RECORD_OR_UNION_TYPE_P (instance)
1896Added line. || TYPE_MAIN_VARIANT (instance) != TYPE_MAIN_VARIANT (node))
1897Added line. continue;
1898Added line.
1899Added line. package_prefix = false;
1900Added line. dump_ada_node (buffer, TYPE_NAME (instance), tmpl, spc, false, true);
1901Added line. dump_template_types (buffer, types, spc);
1902Added line. pp_dot (buffer);
1903Added line. dump_ada_node (buffer, TYPE_NAME (instance), tmpl, spc, false, true);
1904Added line. package_prefix = true;
1905Added line. return true;
1906Added line. }
1907Added line. }
1908Added line.
1909Added line. return false;
1910Added line. }
1911Added line.
18581912/* Dump in BUFFER the contents of all class instantiations associated with
18591913 a given template T. SPC is the indentation level. */
18601914
@@ -1906 +1960 @@dump_ada_template (pretty_printer *buffer, tree t, int spc)
19061960 spc += INDENT_INCR;
19071961 newline_and_indent (buffer, spc);
19081962
1963Added line. tree previous_template = current_template;
1964Added line. tree previous_template_instance = current_template_instance;
1965Added line. current_template = t;
1966Added line. current_template_instance = instance;
19091967 TREE_VISITED (get_underlying_decl (instance)) = 1;
19101968 pp_string (buffer, "type ");
19111969 dump_ada_node (buffer, instance, t, spc, false, true);
@@ -1928 +1986 @@dump_ada_template (pretty_printer *buffer, tree t, int spc)
19281986 dump_ada_node (buffer, instance, t, spc, false, true);
19291987 dump_template_types (buffer, types, spc);
19301988 package_prefix = true;
1989Added line. current_template = previous_template;
1990Added line. current_template_instance = previous_template_instance;
19311991 pp_semicolon (buffer);
19321992 pp_newline (buffer);
19331993 pp_newline (buffer);
@@ -2390 +2450 @@dump_ada_node (pretty_printer *buffer, tree node, tree type, int spc,
23902450 case RECORD_TYPE:
23912451 case UNION_TYPE:
23922452 if (name_only)
2393Removed line. dump_ada_node (buffer, TYPE_NAME (node), node, spc, limited_access,
2394Removed line. true);
2453Added line. {
2454Added line. if (!dump_template_type_name (buffer, node, spc))
2455Added line. dump_ada_node (buffer, TYPE_NAME (node), node, spc, limited_access,
2456Added line. true);
2457Added line. }
23952458 else
23962459 dump_ada_structure (buffer, node, type, false, spc);
23972460 break;
gcc/testsuite/g++.dg/ada-spec/template-instantiation-qualification.C +35−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/template-instantiation-qualification.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Int_Box is Box_int.Box;" } } */
4Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Double_Box is Box_double.Box;" } } */
5Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "item : aliased Box_int.Box;" } } */
6Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "function identity \(value : Box_double.Box\) return Box_double.Box" } } */
7Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Alias_Int_Box is Box_int.Box;" } } */
8Added line.
9Added line. template <typename T>
10Added line. class Box
11Added line. {
12Added line. public:
13Added line. Box ();
14Added line. T value () const;
15Added line.
16Added line. private:
17Added line. T value_;
18Added line. };
19Added line.
20Added line. extern template class Box<int>;
21Added line. extern template class Box<double>;
22Added line.
23Added line. using Int_Box = Box<int>;
24Added line. using Double_Box = Box<double>;
25Added line. template <typename T> using Alias_Box = Box<T>;
26Added line. using Alias_Int_Box = Alias_Box<int>;
27Added line.
28Added line. struct Holder
29Added line. {
30Added line. Box<int> item;
31Added line. };
32Added line.
33Added line. Box<double> identity (Box<double> value);
34Added line.
35Added line. /* { dg-final { cleanup-ada-spec } } */
36

Variant gcc-15-16

Applies to 15.3.0, 16.1.0, 16.2.0. Source flavors: linux, darwin_arm64.

+100 −2 2 files

Download the patch

gcc/c-family/c-ada-spec.cc +65−2modified
Unified diff for gcc/c-family/c-ada-spec.cc: original line, patched line, change, source
@@ -1381 +1381 @@separate_class_package (tree decl)
13811381}
13821382
13831383static bool package_prefix = true;
1384Added line. static tree current_template = NULL_TREE;
1385Added line. static tree current_template_instance = NULL_TREE;
13841386
13851387/* Dump in PP the name of an identifier NODE of type TYPE, following Ada
13861388 syntax. LIMITED_ACCESS indicates whether NODE can be accessed through a
@@ -1902 +1904 @@dump_template_types (pretty_printer *pp, tree types, int spc)
19021904 }
19031905}
19041906
1907Added line. /* If NODE is a concrete template instance emitted in a nested package,
1908Added line. dump the type name owned by that package. */
1909Added line.
1910Added line. static bool
1911Added line. dump_template_type_name (pretty_printer *pp, tree node, int spc)
1912Added line. {
1913Added line. if (!package_prefix || !cpp_check)
1914Added line. return false;
1915Added line.
1916Added line. if (current_template_instance
1917Added line. && TYPE_MAIN_VARIANT (node)
1918Added line. == TYPE_MAIN_VARIANT (current_template_instance))
1919Added line. {
1920Added line. package_prefix = false;
1921Added line. dump_ada_node (pp, current_template_instance, current_template,
1922Added line. spc, false, true);
1923Added line. package_prefix = true;
1924Added line. return true;
1925Added line. }
1926Added line.
1927Added line. for (int i = 0; i < to_dump_count; i++)
1928Added line. {
1929Added line. tree tmpl = to_dump[i];
1930Added line.
1931Added line. if (!cpp_check (tmpl, IS_TEMPLATE))
1932Added line. continue;
1933Added line.
1934Added line. /* DECL_SIZE_UNIT is DECL_TEMPLATE_INSTANTIATIONS in this context. */
1935Added line. for (tree inst = DECL_SIZE_UNIT (tmpl);
1936Added line. inst && inst != error_mark_node;
1937Added line. inst = TREE_CHAIN (inst))
1938Added line. {
1939Added line. tree types = TREE_PURPOSE (inst);
1940Added line. tree instance = TREE_VALUE (inst);
1941Added line.
1942Added line. if (!RECORD_OR_UNION_TYPE_P (instance)
1943Added line. || TYPE_MAIN_VARIANT (instance) != TYPE_MAIN_VARIANT (node))
1944Added line. continue;
1945Added line.
1946Added line. package_prefix = false;
1947Added line. dump_ada_node (pp, TYPE_NAME (instance), tmpl, spc, false, true);
1948Added line. dump_template_types (pp, types, spc);
1949Added line. pp_dot (pp);
1950Added line. dump_ada_node (pp, TYPE_NAME (instance), tmpl, spc, false, true);
1951Added line. package_prefix = true;
1952Added line. return true;
1953Added line. }
1954Added line. }
1955Added line.
1956Added line. return false;
1957Added line. }
1958Added line.
19051959/* Dump in PP the contents of all class instantiations associated with
19061960 a given template T. SPC is the indentation level. */
19071961
@@ -1953 +2007 @@dump_ada_template (pretty_printer *pp, tree t, int spc)
19532007 spc += INDENT_INCR;
19542008 newline_and_indent (pp, spc);
19552009
2010Added line. tree previous_template = current_template;
2011Added line. tree previous_template_instance = current_template_instance;
2012Added line. current_template = t;
2013Added line. current_template_instance = instance;
19562014 TREE_VISITED (get_underlying_decl (instance)) = 1;
19572015 pp_string (pp, "type ");
19582016 dump_ada_node (pp, instance, t, spc, false, true);
@@ -1975 +2033 @@dump_ada_template (pretty_printer *pp, tree t, int spc)
19752033 dump_ada_node (pp, instance, t, spc, false, true);
19762034 dump_template_types (pp, types, spc);
19772035 package_prefix = true;
2036Added line. current_template = previous_template;
2037Added line. current_template_instance = previous_template_instance;
19782038 pp_semicolon (pp);
19792039 pp_newline (pp);
19802040 pp_newline (pp);
@@ -2460 +2520 @@dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
24602520 case RECORD_TYPE:
24612521 case UNION_TYPE:
24622522 if (name_only)
2463Removed line. dump_ada_node (pp, TYPE_NAME (node), node, spc, limited_access,
2464Removed line. true);
2523Added line. {
2524Added line. if (!dump_template_type_name (pp, node, spc))
2525Added line. dump_ada_node (pp, TYPE_NAME (node), node, spc, limited_access,
2526Added line. true);
2527Added line. }
24652528 else
24662529 dump_ada_structure (pp, node, type, false, spc);
24672530 break;
gcc/testsuite/g++.dg/ada-spec/template-instantiation-qualification.C +35−0new file
Unified diff for gcc/testsuite/g++.dg/ada-spec/template-instantiation-qualification.C: original line, patched line, change, source
@@ -0 +1 @@
1Added line. /* { dg-do compile } */
2Added line. /* { dg-options "-fdump-ada-spec-slim" } */
3Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Int_Box is Box_int.Box;" } } */
4Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Double_Box is Box_double.Box;" } } */
5Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "item : aliased Box_int.Box;" } } */
6Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "function identity \(value : Box_double.Box\) return Box_double.Box" } } */
7Added line. /* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Alias_Int_Box is Box_int.Box;" } } */
8Added line.
9Added line. template <typename T>
10Added line. class Box
11Added line. {
12Added line. public:
13Added line. Box ();
14Added line. T value () const;
15Added line.
16Added line. private:
17Added line. T value_;
18Added line. };
19Added line.
20Added line. extern template class Box<int>;
21Added line. extern template class Box<double>;
22Added line.
23Added line. using Int_Box = Box<int>;
24Added line. using Double_Box = Box<double>;
25Added line. template <typename T> using Alias_Box = Box<T>;
26Added line. using Alias_Int_Box = Alias_Box<int>;
27Added line.
28Added line. struct Holder
29Added line. {
30Added line. Box<int> item;
31Added line. };
32Added line.
33Added line. Box<double> identity (Box<double> value);
34Added line.
35Added line. /* { dg-final { cleanup-ada-spec } } */
36

Tests.

template_instantiation_qualification.C C++ · 35 lines
/* { dg-do compile } */
/* { dg-options "-fdump-ada-spec-slim" } */
/* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Int_Box is Box_int.Box;" } } */
/* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Double_Box is Box_double.Box;" } } */
/* { dg-final { scan-file template_instantiation_qualification_c.ads "item : aliased Box_int.Box;" } } */
/* { dg-final { scan-file template_instantiation_qualification_c.ads "function identity \(value : Box_double.Box\) return Box_double.Box" } } */
/* { dg-final { scan-file template_instantiation_qualification_c.ads "subtype Alias_Int_Box is Box_int.Box;" } } */

template <typename T>
class Box
{
public:
  Box ();
  T value () const;

private:
  T value_;
};

extern template class Box<int>;
extern template class Box<double>;

using Int_Box = Box<int>;
using Double_Box = Box<double>;
template <typename T> using Alias_Box = Box<T>;
using Alias_Int_Box = Alias_Box<int>;

struct Holder
{
  Box<int> item;
};

Box<double> identity (Box<double> value);

/* { dg-final { cleanup-ada-spec } } */

Download · View in repository

template_instantiation_qualification.adb Ada · 13 lines
with template_instantiation_qualification_c;
with Ada.Text_IO;

procedure Template_Instantiation_Qualification is
   package Bindings renames template_instantiation_qualification_c;
begin
   if Bindings.Int_Box'Object_Size = 0
     or else Bindings.Alias_Int_Box'Object_Size = 0
   then
      raise Program_Error;
   end if;
   Ada.Text_IO.Put_Line ("PASS C++ Ada template qualification");
end Template_Instantiation_Qualification;

Download · View in repository

run-test.sh shell · 84 lines
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 3 ]]; then
  echo "usage: $0 TOOLCHAIN_ROOT GCC_VERSION unpatched|patched" >&2
  exit 2
fi

root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
source "$root/scripts/regression-common.sh"
resolve_regression_toolchain "$1"
version=$2
state=$3
[[ "$state" == unpatched || "$state" == patched ]] || {
  echo "error: state must be unpatched or patched" >&2
  exit 2
}

gxx="$REGRESSION_TOOLCHAIN/bin/g++"
[[ -x "$gxx" ]] || {
  echo "error: no g++ in $REGRESSION_TOOLCHAIN" >&2
  exit 1
}

cxx_fixture="$root/bundles/cxx-ada-template-qualification/tests/template_instantiation_qualification.C"
ada_fixture="$root/bundles/cxx-ada-template-qualification/tests/template_instantiation_qualification.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-cxx-ada-template-test.XXXXXX")
trap 'rm -rf "$work"' EXIT

for optimization in 0 2; do
  case_dir="$work/O$optimization"
  mkdir -p "$case_dir"
  cp "$cxx_fixture" "$ada_fixture" "$case_dir/"
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$gxx" -c "-O$optimization" -fdump-ada-spec-slim template_instantiation_qualification.C
  )

  spec="$case_dir/template_instantiation_qualification_c.ads"
  [[ -f "$spec" ]] || {
    echo "error: g++ did not generate $spec" >&2
    exit 1
  }

  set +e
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f "-O$optimization" template_instantiation_qualification.adb
  ) >"$case_dir/build.log" 2>&1
  build_status=$?
  set -e

  if [[ "$state" == unpatched ]]; then
    [[ $build_status -ne 0 ]] || {
      echo "error: unpatched C++ Ada template regression unexpectedly compiled at -O$optimization" >&2
      exit 1
    }
    grep -Eiq 'Box.*not visible|multiple use clauses cause hiding' "$case_dir/build.log" || {
      cat "$case_dir/build.log"
      exit 1
    }
    echo "cxx-ada-template-qualification -O$optimization: expected ambiguous type rejection (GCC $version)"
    continue
  fi

  [[ $build_status -eq 0 ]] || {
    cat "$case_dir/build.log"
    exit 1
  }
  grep -F "subtype Int_Box is Box_int.Box;" "$spec"
  grep -F "subtype Double_Box is Box_double.Box;" "$spec"
  grep -F "item : aliased Box_int.Box;" "$spec"
  grep -F "function identity (value : Box_double.Box) return Box_double.Box" "$spec"
  grep -F "subtype Alias_Int_Box is Box_int.Box;" "$spec"
  if grep -Fq "function New_Alias_Box" "$spec"; then
    grep -F "function New_Alias_Box return Alias_Box;" "$spec"
  fi
  "${REGRESSION_ENV[@]}" "$case_dir/template_instantiation_qualification" >"$case_dir/output.log" 2>&1 || {
    cat "$case_dir/output.log"
    exit 1
  }
  grep -F "PASS C++ Ada template qualification" "$case_dir/output.log"
  echo "cxx-ada-template-qualification -O$optimization: patched (GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/cxx-ada-template-qualification/patches/VARIANT.patch
Build the compiler
PATH=/path/to/bootstrap/bin:$PATH ./scripts/build-gnat.sh SOURCE BUILD INSTALL
Run the regression
./scripts/run-regressions.sh INSTALL 1.2.0 GCC_MAJOR patched

Metadata.