[cfe-dev] Interesting clang behavior/bug? on Windows

Richard Smith richard at metafoo.co.uk
Sun Feb 15 18:19:05 PST 2015


On Sat, Feb 14, 2015 at 4:08 PM, Edward Diener <
eldlistmailingz at tropicsoft.com> wrote:

> On 2/14/2015 5:48 PM, Reid Kleckner wrote:
>
>> There might be a Clang bug here, depending on whether the copy ctor is
>> supposed to be implicitly deleted or not. I forget if having a base
>> class with a private copy ctor implicitly deletes derived class copy
>> ctors or not.
>>
>
> The second sentence above is not the case in my example, since there is no
> base class involved. I also did not compile in C++11 mode in my example
> AFAICS. Don't I need a command-line switch of -std=c++11 for that ?


In C++11, the copy constructor would be deleted because the base class dtor
is inaccessible. In C++98, it will not be, because there is no notion of
deleted functions.


> Clang's class dllexport semantics are modeled on MSVC's because they
>> make more sense than GCC's for PE/COFF. As Nico said, the idea is that
>> we emit all these special members (and inline functions!) so that
>> clients can dllimport them and not have to emit definitions. This is an
>> intentional semantic difference from ELF's visibility model, which has a
>> different mechanism for ensuring that the addresses of inline functions
>> are the same across DSO boundaries.
>>
>> If you can find a way to delete, explicitly or implicitly, the unwanted
>> copy ctor, then Clang should skip it while exporting the rest. If not,
>> that's a bug.
>>
>
> Let me start by saying that both mingw/mingw64/gcc on Windows and VC++
> compiles the example I gave without error using their respective Windows
> export keywords. Clang as I have shown does not.
>

Presumably MSVC accepts because they build in C++11 mode (sort of -- they
don't *have* a separate C++98 mode). I would expect Clang to also accept
the code that MSVC accepts when run in C++11 mode.

Whether or not clang generates an implicit copy constructor /
> copy-assignment operator when the user does not have one seems to me to be
> an irrelevant issue. If objects of the class are never copied/assigned why
> should clang generate an error ?


Because, as has already been stated, marking a class as dllexport causes
some of its implicitly-generated methods to be exported, which triggers
their definition. We may be getting the "some" wrong here, or the rules for
mingw and for MSVC might be different, but it *is* correct that we trigger
the definition of some implicit members in some cases when the class is
marked dllexport -- this is necessary for link compatibility with MSVC.

This is regardless of whether the class is exported or not. I have a class
> that cannot be copied/assigned so naturally I see no need to create a
> user-defined copy constructor or copy-assignment operator. How can I be
> wrong not doing so ? Even when I export the class I am still telling users
> of the class that import it that it cannot be copied or assigned. That
> seems pretty clearcut to me. Clang now wants to tell me that if I export
> the class I must change how I code the class as to provide a user-defined
> copy constructor/assignment operator ( or a private declaration/deleted
> function depending on C++03/C++11 ) to satisfy clang ? That is not
> logically right from this end-user's point of view. I am also pretty sure
> that in C++03 if a class is not copyable I am not required to provide a
> user-defined copy constructor or private copy constructor declaration when
> defining the class.
>
> Even in a C++11 compilation is it actually a C++ standard error if the
> programmer does not provide deleted versions of the copy constructor /
> assignment operator when objects of that class cannot be copied / assigned
> using the default generated copy constructor / assignment operator ? I do
> not think so. According to my reading of the C++11 standard as interpreted
> by Lippman in C++ Fifth Edition,
>
> "The synthesized copy constructor is defined as deleted if the class has a
> member whose own copy constructor is deleted or inaccesible..."
>
> "The synthesized copy-assignment operator is defined as deleted if a
> member has a deleted or inaccessible copy-assignment operator..."
>
> I realized I am not quoting from the C++11 standard but unless Lippman is
> in error here I cannot see other than that clang is in error in the case I
> have presented.
>

You're using a language extension, so the guarantees of the C++ standard
are not relevant.

I don't mind filing a bug report on this but I want to get some agreement
> here that clang is not doing the right thing even in the face of an
> "export" of the class. The job of defining an implicit copy constructor and
> copy-assignment operator is that of the compiler and if clang wants to
> claim that in the face of the "export" keyword it is not required to follow
> the standard C++ rules of how thiws should be done, I do not want to follow
> up with such a bug report.
>
>
>> On Fri, Feb 13, 2015 at 11:26 PM, Edward Diener
>> <eldlistmailingz at tropicsoft.com
>> <mailto:eldlistmailingz at tropicsoft.com>> wrote:
>>
>>     On 2/14/2015 1:22 AM, Nico Weber wrote:
>>
>>         On Fri, Feb 13, 2015 at 8:43 PM, Edward Diener
>>         <eldlistmailingz at tropicsoft.__com
>>         <mailto:eldlistmailingz at tropicsoft.com>
>>         <mailto:eldlistmailingz at __tropicsoft.com
>>         <mailto:eldlistmailingz at tropicsoft.com>>>
>>         wrote:
>>
>>              On 2/13/2015 11:17 PM, Nico Weber wrote:
>>
>>                  Hans would know for sure, but I think this is
>>         intentional: Since
>>                  AClass
>>                  is dllexported, all its implicit functions get
>>         generated (so
>>                  that they
>>                  can be exported from the dll), which means they need to
>> be
>>                  semantically
>>                  checked. If you give the class a deleted copy
>>         constructor and
>>                  assignment
>>                  operator, it might work.
>>
>>
>>              Where in the C++ standard is such behavior justified ?
>>
>>
>>         The C++ standard doesn't talk about dllexport, so it won't say
>>         either way.
>>
>>         This isn't a question of standards, it's a question of
>>         practicality. The
>>         way dllexport / dllimport are used is usually like so (think of
>>         a class
>>         that has a valid copy constructor for a bit):
>>
>>         #ifdef IMPL
>>         #define EXPORT __attribute__((dllexport))
>>         #else
>>         #define EXPORT __attribute((dllimport))
>>         #endif
>>
>>         class EXPORT MyClass { ... };
>>
>>         Then, while building your dll, you define IMPL, and while building
>>         users, you don't.
>>
>>
>>     I know this of course.
>>
>>         When the dll is being built, the compiler doesn't know
>>         if clients are going to use MyClass's implicit copy constructor,
>>         so its
>>         choices are:
>>
>>         1.) Generate it when it's dllexported and put it into the dll.
>>         Then, if
>>         clients use it, they'll get it from the dll.
>>         2.) Don't generate it when it's dllexported.
>>         2a) Don't even generate the implementation when clients use it.
>>         Now the
>>         copy constructor isn't defined anywhere and you'll get linker
>>         errors.
>>         2b) Emit a weak definition into every TU that uses it. I think
>> this
>>         would work, but you end up with several copy constructors – if you
>>         wanted this, you probably wouldn't have EXPORTed the class.
>>
>>         Because of this reasoning (I would guess), clang chooses 1 and
>>         defines
>>         all implicit methods for dllexported classes. Now, in your case
>> the
>>         definition of some of these implicit methods causes an error. I
>>         suppose
>>         clang's logic could instead be "only define all implicit methods
>>         that
>>         are valid", but I think this can be different in different TUs.
>>         Consider:
>>
>>             class A;
>>             class EXPORT B { unique_ptr<A> a_; };
>>
>>         I think the implicit destructor of B will be valid in TUs that
>>         happen to
>>         include the header that defines A but not in others. So that
>> doesn't
>>         seem like a good rule.
>>
>>
>>     A TU that does not include the header that defines A is just being
>>     stupid so I think the compiler can choose to assume that an implicit
>>     destructor can be generated. And if there really is no definition
>>     for A then the design of B is just an error. The compiler should
>>     make reasonable choices. For B since C++11 is necesary for
>>     unique_ptr, deleted copy constructors and assignment operators
>>     should be generated if the compiler does not encounter any since
>>     unique_ptr is not copyable or assignable.
>>
>>     The problem is that you are forcing class designers, when a class is
>>     exportable, to generate copy constructors and assignment operators
>>     even when objects of that class are never meant to be copied or
>>     assigned. Of course the practical solution for this is to generate
>>     private declarations in C++03 and deleted declarations in C++11, but
>>     still it seems silly to enforce this. Instead the compiler should
>>     automatically generate these solutions when it sees that a class is
>>     not copyable or when it sees that a class is not assignable, and the
>>     designer has not provided his own declarations.
>>
>>     I really think it is wrong for clang to force class designers, even
>>     when exporting a class, to do something he would ordinarily never
>>     have to do in C++.
>>
>>     BTW gcc works correctly in the same situation in not generating any
>>     error when '__attribute__((dllexport))' is being used for mingw/gcc,
>>     if that means anything to clang.
>>
>>     Also VC++ works correctly in the same situation when
>>     __declspec(dllexport) is being used, if that means anything to clang.
>>
>>     Also as I noticed, when
>>     '__attribute__((__visibility______("default")))' is being used,
>>     which I assume is the Linux equivalent of 'exporting' a class, clang
>>     evidently does the right thing and generates no error. Why does it
>>     therefore operate differently with '__attribute__((dllexport))' ?
>>
>>     Is it a valid Boost solution to use
>>     '__attribute__((__visibility______("default")))' for clang on
>>     Windows targeting mingw/gcc as a workaround for this problem instead
>>     of having to change the exported class itself ? This of course would
>>     not solve the problem of using clang on Windows targeting VC++,
>>     where I assume '__attribute__((dllexport))' is correct and
>>     '__attribute__((__visibility______("default")))' would not be
>>     recognized, but I am less interested in that compiler for Boost.
>>
>>
>>         Nico
>>
>>
>>              The C++ standard does tell us I believe that if an object
>>         of a class
>>              is not copied there is never a need to specify a copy
>>         constructor
>>              and if a class is not assigned there is never a need to
>>         specify an
>>              assignment operator, whether or not one has a non-copyable or
>>              non-assignable member or not. Changing that basic rule to
>>         support
>>              something in clang related to "exported' classes cannot be
>>         correct IMO.
>>
>>              Why does '__attribute__((__visibility______("default")))'
>>         work but
>>              '__attribute__((dllexport))' does not ? Aren't they both the
>>              equivalent of "exporting" a class in clang ?
>>
>>              I realize that for clang on Windows targeting mingw/gcc and
>>         not VC++
>>              that the correct "export" attribute is probably
>>              '__attribute__((__visibility______("default")))' and not
>>              '__attribute__((dllexport))'. But I heavily object to the
>>         idea that
>>              because I might be exporting a class the rules of
>>         copyability or
>>              assignability for that class must change contrary to the C++
>>              standard. This also puts an extra conceptual burden on the
>>         design of
>>              a class.
>>
>>
>>                  On Fri, Feb 13, 2015 at 8:07 PM, Edward Diener
>>                  <eldlistmailingz at tropicsoft.____com
>>                  <mailto:eldlistmailingz at __tropicsoft.com
>>         <mailto:eldlistmailingz at tropicsoft.com>>
>>                  <mailto:eldlistmailingz@
>>         <mailto:eldlistmailingz@>__trop__icsoft.com <
>> http://tropicsoft.com>
>>                  <mailto:eldlistmailingz at __tropicsoft.com
>>         <mailto:eldlistmailingz at tropicsoft.com>>>>
>>                  wrote:
>>
>>                       I am compiling code with "-c -x c++ -O0 -g
>> -fno-inline
>>                  -Wall -g"
>>                       using clang targeting mingw/gcc on Windows.
>>
>>                       This code compiles with no errors (
>>         boost::scoped_ptr<T> is
>>                       non-copyable, non-assignable ):
>>
>>                       #include <boost/scoped_ptr.hpp>
>>                       class
>>                       AClass { boost::scoped_ptr<int> sp_pointer; };
>>                       int main()  {  return 0;  }
>>
>>                       This code compiles with no errors:
>>
>>                       #include <boost/scoped_ptr.hpp>
>>                       class __attribute__((__visibility__(
>> ______"default")))
>>                       AClass { boost::scoped_ptr<int> sp_pointer; };
>>                       int main()  {  return 0;  }
>>
>>                       but this code compiles with errors:
>>
>>                       #include <boost/scoped_ptr.hpp>
>>                       class __attribute__((dllexport))
>>                       AClass { boost::scoped_ptr<int> sp_pointer; };
>>                       int main()  {  return 0;  }
>>
>>                           test_clang_bug.cpp:3:1: error: field of type
>>                           'boost::scoped_ptr<int>' has private copy
>>         constructor
>>                           AClass { boost::scoped_ptr<int> sp_pointer; };
>>                           ^
>>
>>           ..\..\..\boost/smart_ptr/______scoped_ptr.hpp:47:5: note:
>>                  declared
>>                           private here
>>                                scoped_ptr(scoped_ptr const &);
>>                                ^
>>                           test_clang_bug.cpp:3:1: note: implicit copy
>>         constructor for
>>                           'AClass' first required here
>>                           AClass { boost::scoped_ptr<int> sp_pointer; };
>>                           ^
>>                           test_clang_bug.cpp:3:1: error: 'operator=' is
>>         a private
>>                  member
>>                           of 'boost::scoped_ptr<int>'
>>                           AClass { boost::scoped_ptr<int> sp_pointer; };
>>                           ^
>>
>>           ..\..\..\boost/smart_ptr/______scoped_ptr.hpp:48:18:
>>
>>                  note: declared
>>                           private here
>>                                scoped_ptr & operator=(scoped_ptr const &);
>>                                             ^
>>                           test_clang_bug.cpp:3:1: note: implicit copy
>>         assignment
>>                  operator
>>                           for 'AClass' first required here
>>                           AClass { boost::scoped_ptr<int> sp_pointer; };
>>                           ^
>>                           2 errors generated.
>>
>>
>>                       Comments ?
>>
>>                       Should I file a bug report ?
>>
>>                       I do not see why, even "exporting" a class, clang
>>         should
>>                  give an
>>                       error. If I am not copying/assigning an instance
>>         of a class
>>                  I should
>>                       never get a compiler error telling me that some
>>         member is not
>>                       copyable or assignable.
>>
>
>
>
> _______________________________________________
> cfe-dev mailing list
> cfe-dev at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-dev
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20150215/a879c9d2/attachment.html>


More information about the cfe-dev mailing list