Bug 51336 - [C++11] warn on inaccessible template special member functions
Summary: [C++11] warn on inaccessible template special member functions
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.7.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks:
 
Reported: 2011-11-28 18:59 UTC by Marc Glisse
Modified: 2022-02-17 10:41 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Marc Glisse 2011-11-28 18:59:32 UTC
#include <type_traits>
template<class T>
struct A {
        template<class=typename std::enable_if<std::is_same<T,int>::value>::type>
                A(A const&){}
};
constexpr bool b = std::is_abstract<A<double>>::value;

$ g++ -std=c++0x n.cc -c
n.cc: In instantiation of 'struct A<double>':
/tmp/gcc/inst/lib/gcc/x86_64-unknown-linux-gnu/4.7.0/../../../../include/c++/4.7.0/type_traits:530:12:   required from 'struct std::is_abstract<A<double> >'
n.cc:7:47:   required from here
n.cc:5:3: error: no type named 'type' in 'struct std::enable_if<false, void>'

I am not sure what is supposed to happen (that's why I tried), but this result doesn't seem right. Filed under C++ because is_abstract directly forwards to the __is_abstract builtin, but feel free to reassign to libstdc++ if you think the problem is there somehow.

I first tried it with is_copy_constructible (which indirectly calls is_abstract through is_destructible) to check the value it would give (clang+libc++ says is_copy_constructible is true).
Comment 1 Daniel Krügler 2011-11-28 20:49:52 UTC
(In reply to comment #0)
> #include <type_traits>
> template<class T>
> struct A {
>         template<class=typename
> std::enable_if<std::is_same<T,int>::value>::type>
>                 A(A const&){}
> };
> constexpr bool b = std::is_abstract<A<double>>::value;

I *think* the compiler is right to reject this as it currently does, we have *no* sfinae here. When you instantiate A<double>, the declaration of the template constructor is also instantiated, but at that point A<double> is an incomplete type.

IMO you need one further indirection, e.g.

template<class T>
struct A {
  template<class U = T, class = typename
    std::enable_if<std::is_same<U, int>::value>::type
  >
  A(A const&){}
};

Btw.: Neither of these forms can ever prevent the "real" copy constructor to be declared, defined, and used by the compiler.

> I am not sure what is supposed to happen (that's why I tried), but this result
> doesn't seem right. Filed under C++ because is_abstract directly forwards to
> the __is_abstract builtin, but feel free to reassign to libstdc++ if you think
> the problem is there somehow.

Lets look what the compiler-intrinsic people say, above is my first guess on that.
Comment 2 Marc Glisse 2011-11-28 21:17:30 UTC
(In reply to comment #1)
> IMO you need one further indirection, e.g.

Ah, yes, makes sense (although clang accepts both versions).

> Btw.: Neither of these forms can ever prevent the "real" copy constructor to be
> declared, defined, and used by the compiler.

I was experimenting with it because I don't understand why this code (your fixed version) is valid if that declaration has no effect... (well, it does remove the implicit A(), but that doesn't count)

Maybe I should ask for a warning?
Comment 3 Daniel Krügler 2011-11-29 07:08:45 UTC
(In reply to comment #1)
> When you instantiate A<double>, the declaration of the template constructor is 
> also instantiated, but at that point A<double> is an incomplete type.

I just recognize that the last part of this is a bit misleading, please read this as:

"When you instantiate A<double>, the declaration of the template constructor is 
also instantiated, but this turns out to be an ill-formed signature"

The argument in regard to type-completeness was intended to point to the library specification, which requires A<double> to be complete, which means that A<double> may be instantiated including it's member declarations, which again is the cause of the error, because we have no sfinae situation here.

In regard to the __is_abstract builtin question it seems that in simple cases like these (no dependent base classes involved), it may be possible to implement the intrinsic without instantiating A<double>, but maybe the current behavior is actually better, because it makes portable programming easier;-)
Comment 4 Paolo Carlini 2011-11-29 10:11:34 UTC
The issue with type_traits intrinsics vs instantiation came up recently, when I fixed an actual bug affecting __is_base_of. Note, if isn't clear already, that in the case of is_abstract, C++11 requires unconditionally completeness for T: we are treating all the traits having such type of precondition in an uniform way. All in all, I agree with Daniel about portability.
Comment 5 Marc Glisse 2011-11-29 10:27:18 UTC
All right, now the is_abstract behavior is settled, do you think the fixed code provided by Daniel in comment #1 should produce a warning, since the declaration is absolutely useless (I may be missing something)? Or maybe there are legitimate meta-programming tricks I am not thinking of that would turn regular constructors into pseudo copy constructors to disable them?
Comment 6 Marc Glisse 2011-11-29 10:34:08 UTC
(In reply to comment #5)
> All right, now the is_abstract behavior is settled, do you think the fixed code
> provided by Daniel in comment #1 should produce a warning, since the
> declaration is absolutely useless (I may be missing something)?

Ah, I did find a case where it was used:
I added a A(A&) constructor to prevent the default A(A const&) from being generated, and then the template version was used when copying from const (only if T is int).

Closing the bug as invalid, thanks for your answers.
Comment 7 Daniel Krügler 2011-11-29 10:50:37 UTC
(In reply to comment #5)
> All right, now the is_abstract behavior is settled, do you think the fixed code
> provided by Daniel in comment #1 should produce a warning, since the
> declaration is absolutely useless (I may be missing something)? 

IMO a warning could be very useful here (at least in circumstances where the constructor is never reachable).

> Or maybe there are legitimate meta-programming tricks I am not thinking of that 
> would turn regular constructors into pseudo copy constructors to disable them?

While it seems that the current defect in regard to concept-constrained member functions mentioned in c++std-core-20783 is a defect, so that

template<ObjectType T>
class A {
 requires SomeConcept<T>
 A(const A&) {}
};

is *intended* to work, I currently see no such chance for sfinae-constrained special-member functions - unless the new temploid nomenclature shows that in

template<class T>
struct A {
  template<class U = T, class = typename
    std::enable_if<std::is_same<U, int>::value>::type
  >
  A(A const&){}
};

A<T>::A(A const&) is considered as a temploid as well. I stay tuned to see how "temploids" will be defined...

Your suggested addition of a copy-constructor to non-const is surely useful in some cases, but I think the emulation is imperfect. Just consider that you try to copy from a source that is not const.
Comment 8 Marc Glisse 2011-11-29 11:26:19 UTC
(In reply to comment #7)
> IMO a warning could be very useful here (at least in circumstances where the
> constructor is never reachable).

Ok, reopening then. Not sure how easy it is to test for reachability in general, but there are certainly easy cases where it is doable.

> While it seems that the current defect in regard to concept-constrained member
> functions mentioned in c++std-core-20783 is a defect, so that
> 
> template<ObjectType T>
> class A {
>  requires SomeConcept<T>
>  A(const A&) {}
> };
> 
> is *intended* to work,

That would be great. I assume that when T doesn't satisfy SomeConcept, the compiler can still generate a default copy constructor (we can always have a deleted copy constructor with requires !SomeConcept<T> if we don't want it).

> I currently see no such chance for sfinae-constrained
> special-member functions - unless the new temploid nomenclature shows that in
> 
> template<class T>
> struct A {
>   template<class U = T, class = typename
>     std::enable_if<std::is_same<U, int>::value>::type
>   >
>   A(A const&){}
> };
> 
> A<T>::A(A const&) is considered as a temploid as well. I stay tuned to see how
> "temploids" will be defined...

Looks interesting, although since we're talking about a future standard (at least I assume that's what you are talking about? Or are temploids coming up as a bugfix for C++11?), I'd rather write (see around c++std-ext-11764):
static if(std::is_same<T,int>())
A(A const&){ /* special code */ }

> Your suggested addition of a copy-constructor to non-const is surely useful in
> some cases, but I think the emulation is imperfect. Just consider that you try
> to copy from a source that is not const.

I completely agree, I was just trying to see if there was any possibility for the templated "copy" constructor to have any effect (not even necessarily a useful one). If there had been none, a warning was definitely warranted.
Comment 9 Daniel Krügler 2011-11-29 11:36:34 UTC
(In reply to comment #8)
> Looks interesting, although since we're talking about a future standard (at
> least I assume that's what you are talking about? Or are temploids coming up as
> a bugfix for C++11?)

The term "temploid" will very probably be introduced for C++11 already, we have a lot of core issues that could be resolved much easier with that concept (I'm not referring here to language-concepts, just to make that clear).
Comment 10 Jonathan Wakely 2022-02-17 10:41:26 UTC
The name temploid got replaced by "templated entity", see [temp.pre].