c++: alias template and empty parameter packs [PR104008]
authorMarek Polacek <polacek@redhat.com>
Wed, 16 Mar 2022 13:34:34 +0000 (09:34 -0400)
committerMarek Polacek <polacek@redhat.com>
Fri, 18 Mar 2022 16:50:18 +0000 (12:50 -0400)
Zero-length pack expansions are treated as if no list were provided
at all, that is, with

  template<typename...> struct S { };
  template<typename T, typename... Ts>
  void g() {
    S<std::is_same<T, Ts>...>;
  }

g<int> will result in S<>.  In the following test we have something
similar:

  template <typename T, typename... Ts>
  using IsOneOf = disjunction<is_same<T, Ts>...>;

and then we have "IsOneOf<OtherHolders>..." where OtherHolders is an
empty pack.  Since r11-7931, we strip_typedefs in TYPE_PACK_EXPANSION.
In this test that results in "IsOneOf<OtherHolders>" being turned into
"disjunction<>".  So the whole expansion is now "disjunction<>...".  But
then we error in make_pack_expansion because find_parameter_packs_r won't
find the pack OtherHolders.

We strip the alias template because dependent_alias_template_spec_p says
it's not dependent.  It it not dependent because this alias is not
TEMPLATE_DECL_COMPLEX_ALIAS_P.  My understanding is that currently we
consider an alias complex if it

1) expands a pack from the enclosing class, as in

    template<template<typename... U> typename... TT>
    struct S {
      template<typename... Args>
      using X = P<TT<Args...>...>;
    };

   where the alias expands TT; or

2) the expansion does *not* name all the template parameters, as in

    template<typename...> struct R;
    template<typename T, typename... Ts>
    using U = R<X<Ts>...>;

   where T is not named in the expansion.

But IsOneOf is neither.  And it can't know how it's going to be used.
Therefore I think we cannot make it complex (and in turn dependent) to fix
this bug.

After much gnashing of teeth, I think we simply want to avoid stripping
the alias if the new pattern doesn't have any parameter packs to expand.

PR c++/104008

gcc/cp/ChangeLog:

* tree.cc (strip_typedefs): Don't strip an alias template when
doing so would result in losing a parameter pack.

gcc/testsuite/ChangeLog:

* g++.dg/cpp0x/variadic-alias3.C: New test.
* g++.dg/cpp0x/variadic-alias4.C: New test.

gcc/cp/tree.cc
gcc/testsuite/g++.dg/cpp0x/variadic-alias3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp0x/variadic-alias4.C [new file with mode: 0644]

index 6e9be71..eb59e56 100644 (file)
@@ -1778,7 +1778,18 @@ strip_typedefs (tree t, bool *remove_attributes, unsigned int flags)
        if (TYPE_P (pat))
          {
            type = strip_typedefs (pat, remove_attributes, flags);
-           if (type != pat)
+           /* Empty packs can thwart our efforts here.  Consider
+
+               template <typename T, typename... Ts>
+               using IsOneOf = disjunction<is_same<T, Ts>...>;
+
+             where IsOneOf seemingly uses all of its template parameters in
+             its expansion (and does not expand a pack from the enclosing
+             class), so the alias is not marked as complex.  However, it may
+             be used as in "IsOneOf<Ts>", where Ts is an empty parameter pack,
+             and stripping it down into "disjunction<>" here would exclude the
+             Ts pack, resulting in an error.  */
+           if (type != pat && uses_parameter_packs (type))
              {
                result = copy_node (t);
                PACK_EXPANSION_PATTERN (result) = type;
diff --git a/gcc/testsuite/g++.dg/cpp0x/variadic-alias3.C b/gcc/testsuite/g++.dg/cpp0x/variadic-alias3.C
new file mode 100644 (file)
index 0000000..6b6dd9f
--- /dev/null
@@ -0,0 +1,45 @@
+// PR c++/104008
+// { dg-do compile { target c++11 } }
+
+template <typename...> struct conjunction;
+template <typename...> struct disjunction;
+template <typename, typename> struct is_same;
+template <bool> struct enable_if;
+template <bool _Cond> using enable_if_t = typename enable_if<_Cond>::type;
+struct B;
+struct __uniq_ptr_impl {
+  struct _Ptr {
+    using type = B *;
+  };
+  using pointer = _Ptr::type;
+};
+struct unique_ptr {
+  using pointer = __uniq_ptr_impl::pointer;
+  unique_ptr(pointer);
+};
+template <typename T, typename... Ts>
+using IsOneOf = disjunction<is_same<T, Ts>...>;
+
+template <typename...> struct any_badge;
+
+struct badge {
+  badge(any_badge<>);
+  badge();
+};
+
+template <typename...> struct any_badge {
+  template <typename... OtherHolders,
+            enable_if_t<conjunction<IsOneOf<OtherHolders>...>::value>>
+  any_badge();
+};
+
+template <typename, typename... _Args> unique_ptr make_unique(_Args... __args);
+
+struct B {
+  B(badge);
+  unique_ptr b_ = make_unique<B>(badge{});
+};
+
+template <typename, typename... _Args> unique_ptr make_unique(_Args... __args) {
+  return new B(__args...);
+}
diff --git a/gcc/testsuite/g++.dg/cpp0x/variadic-alias4.C b/gcc/testsuite/g++.dg/cpp0x/variadic-alias4.C
new file mode 100644 (file)
index 0000000..896a472
--- /dev/null
@@ -0,0 +1,48 @@
+// PR c++/104008
+// { dg-do compile { target c++11 } }
+// Differs from variadic-alias3.C only in the pattern of a pack expansion
+// in line 34.  But it's important to check that we also deal with more
+// complex patterns.
+
+template <typename...> struct conjunction;
+template <typename...> struct disjunction;
+template <typename, typename> struct is_same;
+template <bool> struct enable_if;
+template <bool _Cond> using enable_if_t = typename enable_if<_Cond>::type;
+struct B;
+struct __uniq_ptr_impl {
+  struct _Ptr {
+    using type = B *;
+  };
+  using pointer = _Ptr::type;
+};
+struct unique_ptr {
+  using pointer = __uniq_ptr_impl::pointer;
+  unique_ptr(pointer);
+};
+template <typename T, typename... Ts>
+using IsOneOf = disjunction<is_same<T, Ts>...>;
+
+template <typename...> struct any_badge;
+
+struct badge {
+  badge(any_badge<>);
+  badge();
+};
+
+template <typename...> struct any_badge {
+  template <typename... OtherHolders,
+            enable_if_t<conjunction<IsOneOf<OtherHolders>&...>::value>>
+  any_badge();
+};
+
+template <typename, typename... _Args> unique_ptr make_unique(_Args... __args);
+
+struct B {
+  B(badge);
+  unique_ptr b_ = make_unique<B>(badge{});
+};
+
+template <typename, typename... _Args> unique_ptr make_unique(_Args... __args) {
+  return new B(__args...);
+}