diff mbox

[C++,RFC] Fix up attribute handling in templates (PR c++/79502)

Message ID CADzB+2=6yb+1Jf2xA6to+UDHBh6sqVk1M4A0o8Q3nqdDa0Kvxw@mail.gmail.com
State New
Headers show

Commit Message

Jason Merrill Feb. 16, 2017, 7:49 p.m. UTC
On Thu, Feb 16, 2017 at 11:33 AM, Jakub Jelinek <jakub@redhat.com> wrote:
>         PR c++/79502
>         * pt.c (apply_late_template_attributes): If there are
>         no dependent attributes, set *p to attributes.  If there were
>         some attributes in *p previously with or without dependent
>         attributes, chain them after the new attributes.

Here's the variant of your patch that I'm applying.
commit 3a5098ff79743f89c2e0e3cfa6a00ee82ec26b78
Author: Jason Merrill <jason@redhat.com>
Date:   Thu Feb 16 13:05:56 2017 -0500

            PR c++/79502 - lost nodiscard attribute
    
            * pt.c (apply_late_template_attributes): Do apply non-dependent
            attributes to types.

Comments

Martin Sebor Feb. 16, 2017, 11:13 p.m. UTC | #1
On 02/16/2017 12:49 PM, Jason Merrill wrote:
> On Thu, Feb 16, 2017 at 11:33 AM, Jakub Jelinek <jakub@redhat.com> wrote:
>>         PR c++/79502
>>         * pt.c (apply_late_template_attributes): If there are
>>         no dependent attributes, set *p to attributes.  If there were
>>         some attributes in *p previously with or without dependent
>>         attributes, chain them after the new attributes.
>
> Here's the variant of your patch that I'm applying.

Sorry to butt in but I feel like I'm missing something basic.  Are
these attributes (nodiscard, noreturn, maybe_unused, and deprecated)
meant to apply to templates?  The text in for nodiscard suggests
they're not:

   The attribute-token nodiscard may be applied to the declarator-id
   in a function declaration or to the declaration of a class or
   enumeration.

Noreturn also doesn't mention templates:

   The attribute may be applied to the declarator-id in a function
   declaration.

Deprecated explicitly mentions template specializations but not
primary templates:

   The attribute may be applied to the declaration of a class,
    a typedef-name, a variable, a non-static data member, a function,
    a namespace, an enumeration, an enumerator, or a template
    specialization.

I can certainly see how applying attributes to the primary template
would be useful so it's puzzling to me that the standard seems to
preclude it.

I ask also because I was just looking at bug 79021 and scratching
my head about what to thing about it.   While trying to understand
how GCC handles attributes for the primary template I came across
what doesn't make sense to me.   Why would it apply the attribute
from the primary to the explicit specialization when the two are
distinct entities?  Is that a bug?

template <class T>
[[noreturn]] int f () { throw ""; }

template <> int f<void> () { return 0; }
t.C: In function ‘int f() [with T = void]’:
t.C:4:37: warning: function declared ‘noreturn’ has a ‘return’ statement
  template <> int f<void> () { return 0; }
                                      ^
t.C:4:37: warning: ‘noreturn’ function does return
  template <> int f<void> () { return 0; }
                                      ^

Clang complains on this too with similar errors, but then GCC
silently accepts this code (which makes sense to me) which Clang
5 rejects with what looks like a bug:

template <class T>
int g () { return 0; }

template <> [[noreturn]] int g<void> () { throw ""; }
template <> [[noreturn]] int g<int> () { throw ""; }

Thanks
Martin
Jason Merrill Feb. 18, 2017, 4:53 a.m. UTC | #2
On Thu, Feb 16, 2017 at 6:13 PM, Martin Sebor <msebor@gmail.com> wrote:
> On 02/16/2017 12:49 PM, Jason Merrill wrote:
>>
>> On Thu, Feb 16, 2017 at 11:33 AM, Jakub Jelinek <jakub@redhat.com> wrote:
>>>
>>>         PR c++/79502
>>>         * pt.c (apply_late_template_attributes): If there are
>>>         no dependent attributes, set *p to attributes.  If there were
>>>         some attributes in *p previously with or without dependent
>>>         attributes, chain them after the new attributes.
>>
>>
>> Here's the variant of your patch that I'm applying.
>
>
> Sorry to butt in but I feel like I'm missing something basic.  Are
> these attributes (nodiscard, noreturn, maybe_unused, and deprecated)
> meant to apply to templates?  The text in for nodiscard suggests
> they're not:
>
>   The attribute-token nodiscard may be applied to the declarator-id
>   in a function declaration or to the declaration of a class or
>   enumeration.
>
> Noreturn also doesn't mention templates:
>
>   The attribute may be applied to the declarator-id in a function
>   declaration.
>
> Deprecated explicitly mentions template specializations but not
> primary templates:
>
>   The attribute may be applied to the declaration of a class,
>    a typedef-name, a variable, a non-static data member, a function,
>    a namespace, an enumeration, an enumerator, or a template
>    specialization.
>
> I can certainly see how applying attributes to the primary template
> would be useful so it's puzzling to me that the standard seems to
> preclude it.

I don't think it's precluded; a /template-declaration/ syntactically
includes a /declaration/, so in general any statement about e.g. a
function declaration also applies to a function template declaration.

> I ask also because I was just looking at bug 79021 and scratching
> my head about what to thing about it.   While trying to understand
> how GCC handles attributes for the primary template I came across
> what doesn't make sense to me.   Why would it apply the attribute
> from the primary to the explicit specialization when the two are
> distinct entities?  Is that a bug?

This seems like a Core issue; the standard says nothing about how
attributes on a template affect specializations.

I think that as a general rule, not applying attributes from the
template to specializations makes sense.  There will be some
exceptions, such as the abi_tag attribute which is a property of the
name rather than a particular declaration.

Jason
Martin Sebor Feb. 19, 2017, 8:29 p.m. UTC | #3
On 02/17/2017 09:53 PM, Jason Merrill wrote:
> On Thu, Feb 16, 2017 at 6:13 PM, Martin Sebor <msebor@gmail.com> wrote:
>> On 02/16/2017 12:49 PM, Jason Merrill wrote:
>>>
>>> On Thu, Feb 16, 2017 at 11:33 AM, Jakub Jelinek <jakub@redhat.com> wrote:
>>>>
>>>>         PR c++/79502
>>>>         * pt.c (apply_late_template_attributes): If there are
>>>>         no dependent attributes, set *p to attributes.  If there were
>>>>         some attributes in *p previously with or without dependent
>>>>         attributes, chain them after the new attributes.
>>>
>>>
>>> Here's the variant of your patch that I'm applying.
>>
>>
>> Sorry to butt in but I feel like I'm missing something basic.  Are
>> these attributes (nodiscard, noreturn, maybe_unused, and deprecated)
>> meant to apply to templates?  The text in for nodiscard suggests
>> they're not:
>>
>>   The attribute-token nodiscard may be applied to the declarator-id
>>   in a function declaration or to the declaration of a class or
>>   enumeration.
>>
>> Noreturn also doesn't mention templates:
>>
>>   The attribute may be applied to the declarator-id in a function
>>   declaration.
>>
>> Deprecated explicitly mentions template specializations but not
>> primary templates:
>>
>>   The attribute may be applied to the declaration of a class,
>>    a typedef-name, a variable, a non-static data member, a function,
>>    a namespace, an enumeration, an enumerator, or a template
>>    specialization.
>>
>> I can certainly see how applying attributes to the primary template
>> would be useful so it's puzzling to me that the standard seems to
>> preclude it.
>
> I don't think it's precluded; a /template-declaration/ syntactically
> includes a /declaration/, so in general any statement about e.g. a
> function declaration also applies to a function template declaration.

I'm sure you're right that it's not meant to be precluded, otherwise
the standard library couldn't declare noreturn the throw_with_nested
function template (the only template declared noreturn in the library
I could find).

FWIW, a context where noreturn clearly is not applicable is function
pointers.  G++ accepts it there but then ignores it.  Clang rejects
it.  I would think accepting it on function pointers would be useful
as well.  GCC (in C mode) accepts __attribute__ noreturn on function
pointers and treats them as such (G++ does not).

Another interesting context is the explicit instantiation directive.
Again, the standard seems clear that noreturn isn't allowed but GCC
and Microsoft Visual C++ both accept it. Clang and EDG reject it.
I think rejecting it here makes sense and accepting is a bug.

>> I ask also because I was just looking at bug 79021 and scratching
>> my head about what to thing about it.   While trying to understand
>> how GCC handles attributes for the primary template I came across
>> what doesn't make sense to me.   Why would it apply the attribute
>> from the primary to the explicit specialization when the two are
>> distinct entities?  Is that a bug?
>
> This seems like a Core issue; the standard says nothing about how
> attributes on a template affect specializations.

Yes, I thought so as well.  Thanks for confirming that.  Unless
you would prefer to bring it up yourself let me post it to the
core reflector and suggest to open a new issue for it.

> I think that as a general rule, not applying attributes from the
> template to specializations makes sense.  There will be some
> exceptions, such as the abi_tag attribute which is a property of the
> name rather than a particular declaration.

That makes sense to me.

Thanks
Martin
diff mbox

Patch

diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 712fb69..c468268 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -10073,29 +10073,43 @@  apply_late_template_attributes (tree *decl_p, tree attributes, int attr_flags,
   tree t;
   tree *p;
 
-  for (t = attributes; t; t = TREE_CHAIN (t))
-    if (ATTR_IS_DEPENDENT (t))
-      {
-	last_dep = t;
-	attributes = copy_list (attributes);
-	break;
-      }
+  if (attributes == NULL_TREE)
+    return;
 
   if (DECL_P (*decl_p))
     {
       if (TREE_TYPE (*decl_p) == error_mark_node)
 	return;
       p = &DECL_ATTRIBUTES (*decl_p);
+      /* DECL_ATTRIBUTES comes from copy_node in tsubst_decl, and identical
+         to our attributes parameter.  */
+      gcc_assert (*p == attributes);
     }
   else
-    p = &TYPE_ATTRIBUTES (*decl_p);
+    {
+      p = &TYPE_ATTRIBUTES (*decl_p);
+      /* TYPE_ATTRIBUTES was set up (with abi_tag and may_alias) in
+	 lookup_template_class_1, and should be preserved.  */
+      gcc_assert (*p != attributes);
+      while (*p)
+	p = &TREE_CHAIN (*p);
+    }
 
+  for (t = attributes; t; t = TREE_CHAIN (t))
+    if (ATTR_IS_DEPENDENT (t))
+      {
+	last_dep = t;
+	attributes = copy_list (attributes);
+	break;
+      }
+
+  *p = attributes;
   if (last_dep)
     {
       tree late_attrs = NULL_TREE;
       tree *q = &late_attrs;
 
-      for (*p = attributes; *p; )
+      for (; *p; )
 	{
 	  t = *p;
 	  if (ATTR_IS_DEPENDENT (t))
diff --git a/gcc/testsuite/g++.dg/cpp0x/attrib54.C b/gcc/testsuite/g++.dg/cpp0x/attrib54.C
new file mode 100644
index 0000000..e5817c3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/attrib54.C
@@ -0,0 +1,21 @@ 
+// { dg-do compile { target c++11 } }
+
+inline namespace N __attribute__((__abi_tag__ ("foo"))) {}
+template <typename> struct A {};
+namespace N {
+template <typename> class B {};
+}
+template <typename T> class __attribute__((__aligned__ (sizeof (T)))) C {};
+template <typename> struct D {
+  template <typename _Up> using G = C<_Up>;
+};
+template <typename T> struct F {
+  template <typename U> struct H {
+    typedef typename D<T>::template G<U> I;
+  };
+};
+template <typename T, typename = C<T>> struct J {
+  C<A<const B<char>>> L;
+  typedef F<C<int>>::H<A<const B<char>>>::I M;
+  J<M> *a;
+};
diff --git a/gcc/testsuite/g++.dg/cpp0x/attrib55.C b/gcc/testsuite/g++.dg/cpp0x/attrib55.C
new file mode 100644
index 0000000..79d0c8c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/attrib55.C
@@ -0,0 +1,21 @@ 
+// { dg-do compile { target c++11 } }
+
+inline namespace N __attribute__((__abi_tag__ ("foo"))) {}
+template <typename> struct A {};
+namespace N {
+template <typename> class B {};
+}
+template <typename T> class __attribute__((__unused__)) C {};
+template <typename> struct D {
+  template <typename _Up> using G = C<_Up>;
+};
+template <typename T> struct F {
+  template <typename U> struct H {
+    typedef typename D<T>::template G<U> I;
+  };
+};
+template <typename T, typename = C<T>> struct J {
+  C<A<const B<char>>> L;
+  typedef F<C<int>>::H<A<const B<char>>>::I M;
+  J<M> *a;
+};
diff --git a/gcc/testsuite/g++.dg/cpp1z/nodiscard4.C b/gcc/testsuite/g++.dg/cpp1z/nodiscard4.C
new file mode 100644
index 0000000..8a95c94
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1z/nodiscard4.C
@@ -0,0 +1,14 @@ 
+// PR c++/79502
+// { dg-do compile { target c++11 } }
+
+template<typename>
+struct [[nodiscard]] missiles {};
+
+missiles<void> make() { return {}; }
+missiles<void> (*fnptr)() = make;
+
+int main()
+{
+  make();	// { dg-warning "ignoring returned value of type" }
+  fnptr();	// { dg-warning "ignoring returned value of type" }
+}
diff --git a/gcc/testsuite/g++.dg/ext/attrib53.C b/gcc/testsuite/g++.dg/ext/attrib53.C
new file mode 100644
index 0000000..408433d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/attrib53.C
@@ -0,0 +1,21 @@ 
+// { dg-do compile { target c++11 } }
+
+inline namespace N __attribute__((__abi_tag__ ("foo"))) {}
+template <typename> struct A;
+namespace N {
+template <typename> class B;
+}
+template <typename> class C {};
+template <typename> struct D {
+  template <typename _Up> using G = C<_Up>;
+};
+template <typename T> struct F {
+  template <typename U> struct H {
+    typedef typename D<T>::template G<U> I;
+  };
+};
+template <typename T, typename = C<T>> struct J {
+  C<A<const B<char>>> L;
+  typedef F<C<int>>::H<A<const B<char>>>::I M;
+  J<M> *a;
+};