This is an archive of the discontinued LLVM Phabricator instance.

shared_ptr changes from library fundamentals (P0414R2)
ClosedPublic

Authored by zoecarver on May 22 2019, 8:44 AM.

Diff Detail

Event Timeline

zoecarver created this revision.May 22 2019, 8:44 AM
zoecarver edited the summary of this revision. (Show Details)
  • fully implement the paper
  • add tests
  • Add unsupported and _NOEXCEPT. Now works in C++03.
zoecarver marked an inline comment as done.May 27 2019, 11:40 AM
zoecarver added inline comments.
test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/pointer.pass.cpp
42 ↗(On Diff #201558)

I am not so sure about shared_ptr<void>. The standard says "The expression delete[] p, when T is an array type, or delete p, when T is not an array type, shall have well-defined behavior, and shall not throw exceptions." Meaning that delete T must be well defined. But the standard also says delete void is not well defined: "This implies that an object cannot be deleted using a pointer of type void* because there are no objects of type void." Therefore, I think shared_ptr<void> goes against the standard.

Either way, for the overload tested here, we will have to pick between creating a deleter for type T and type Y. The former works for arrays and the latter works for void.

zoecarver marked an inline comment as done.May 31 2019, 7:17 PM
zoecarver added inline comments.
include/memory
3741 ↗(On Diff #201558)

Use add_lvalue_reference.

zoecarver marked an inline comment as done.Jun 6 2019, 7:38 PM
zoecarver added inline comments.
test/std/utilities/memory/util.smartptr/util.smartptr.shared/util.smartptr.shared.const/pointer.pass.cpp
42 ↗(On Diff #201558)

The standard says delete p must be well defined. Meaning delete Y must be well defined. Not delete T. My bad.

zoecarver updated this revision to Diff 203479.Jun 6 2019, 7:59 PM
  • fix test issues
  • fix diff issues
  • fix void issues
zoecarver marked an inline comment as done.Jun 6 2019, 8:00 PM
zoecarver added inline comments.
include/memory
4028 ↗(On Diff #203479)

I don't love this "hack", but I am not sure there is a better way to fix the issue I describe below. Suggestions are more than welcome.

zoecarver updated this revision to Diff 246976.Feb 27 2020, 9:10 AM
  • Rebase off master
  • Remove references to D66177
  • Incorporate changes (tests) from D62103
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptFeb 27 2020, 9:10 AM
zoecarver updated this revision to Diff 246978.Feb 27 2020, 9:11 AM
  • Diff from master (not whatever phabricator selected...)
zoecarver updated this revision to Diff 246980.Feb 27 2020, 9:14 AM
  • Remove accidentally added files (.rej and weak_ptr_Y)
zoecarver updated this revision to Diff 246981.Feb 27 2020, 9:17 AM
  • Remove remaining .rej files

    Sorry for all the updates.
  • Run everything through clang-format
  • Fix failing test after formatting change

Ping. This patch now has no references to any other patches so it can now be merged immediately.

Herald added a reviewer: Restricted Project. · View Herald TranscriptMar 14 2020, 2:39 PM

I would really like to make some progress on this patch. What are the next steps?

I would really like to make some progress on this patch. What are the next steps?

Pinging ~weekly is about the best we've got. Folks might be a bit less responsive during the current global pandemic, etc.

@dblaikie fair point. Here's the weekly ping.

ldionne requested changes to this revision.May 7 2020, 10:24 AM

Thanks for your patience, I'm finally getting to this. This mostly LGTM, with a question:

For get and operator[], the paper says:

Remarks: When T is [not] an array type, it is unspecified whether this member function is declared. If it is declared, it is unspecified what its return type is, except that the declaration (although not necessarily the definition) of the function shall be well formed.

I think we might want to add a static_assert in the body of those functions to check that _T is/is not an array type. WDYT?

libcxx/www/cxx1z_status.html
131

You can mark this as being in LLVM 11.0.

This revision now requires changes to proceed.May 7 2020, 10:24 AM
zoecarver marked an inline comment as done.EditedMay 7 2020, 4:00 PM

Thanks for the review :)

@ldionne I think that's only the case for operator[], not get(). In fact, in the paper, [[ https://eel.is/c++draft/smartptr#util.smartptr.shared.obs-9 | get() is used to implement operator[]. ]]

zoecarver updated this revision to Diff 262782.May 7 2020, 4:09 PM
  • Add static assert in opeartor[]
  • Mark as complete in LLVM 11.0
ldionne requested changes to this revision.May 7 2020, 8:35 PM

Thanks for the review :)

@ldionne I think that's only the case for operator[], not get(). In fact, in the paper, [[ https://eel.is/c++draft/smartptr#util.smartptr.shared.obs-9 | get() is used to implement operator[]. ]]

You are entirely right, I got confused by the code highlighting in the paper. Instead, it was:

T* operator->() const noexcept;

Specifically, this bullet: http://wg21.link/util.smartptr.shared.obs#7.

If it makes sense to add a static_assert in operator[], I think it makes sense to add the reverse static_assert in operator->.

This revision now requires changes to proceed.May 7 2020, 8:35 PM
zoecarver updated this revision to Diff 262961.May 8 2020, 2:33 PM
  • Add static assertion to operator->
zoecarver updated this revision to Diff 262962.May 8 2020, 2:34 PM
  • Run clang-format on tests
ldionne accepted this revision.May 11 2020, 5:24 AM
This revision is now accepted and ready to land.May 11 2020, 5:24 AM
This revision was automatically updated to reflect the committed changes.
cedral added a subscriber: cedral.EditedAug 3 2021, 10:28 AM

using static_assert breaks the ability to include <memory> in a precompiled header and then explicitly define a shared pointer type for export. You should have used enable_if on the return type instead. you can download my Xcode 12 project files from southwell.org or create your own based on the following three code files. test.cpp will fail to compile because it attempts to instantiate the [] lookup operator for export. I don't think this is a bug. It needs to do this because it doesn't know which member functions will be called when the exported type is used by clients of the library. Though this does appear to work if memory is not first included in a precompiled header so I could be misinterpreting that.

PrefixHeader.pch

#include <memory>

testtemplate.hpp

#ifndef testtemplate_
#define testtemplate_

#include <memory>

template class __attribute__ ((visibility ("default"))) std::shared_ptr<int>;

typedef std::shared_ptr<int> shared_int;

__attribute__ ((visibility ("default"))) shared_int get_int();

#endif

testtemplate.cpp

#include "testtemplate.hpp"
#include <cstdlib>

shared_int get_int()
{
  return std::make_shared<int>(std::rand());
}
Quuxplusone added inline comments.
libcxx/include/memory
3912–3913

@cedral wrote:

using static_assert breaks the ability to include <memory> in a precompiled header and then explicitly define a shared pointer type for export. You should have used enable_if on the return type instead. [...] test.cpp will fail to compile because it attempts to instantiate the [] lookup operator for export. I don't think this is a bug. It needs to do this because it doesn't know which member functions will be called when the exported type is used by clients of the library.

On the one hand, I confirm your understanding of how template class std::shared_ptr<int[]> is treated by the compiler. https://godbolt.org/z/7bbasY83G
On the other hand, I cannot reproduce your issue with libc++ itself; the reason is that _LIBCPP_INLINE_VISIBILITY expands to a series of attributes including __attribute__((__exclude_from_explicit_instantiation__)). If you're seeing the problem only with PCHes, I wonder if there's a bad interaction between PCHes and that attribute.
Alternatively, are you using GCC (or anything else non-Clang)? It seems like GCC doesn't support that attribute, so I think on GCC you're just out of luck by design.

On the third hand, the language-lawyer answer is that your whole program is "ill-formed, no diagnostic required," and so libc++ isn't required to make it work anyway. Users aren't allowed to explicitly instantiate library types like that. One reason is that you don't know the name of the type — is it really class std::shared_ptr, or is it struct std::shared_ptr, or is shared_ptr just an alias for __shared_ptr, etc. etc. The more important reason is exactly what you already found out: When you explicitly instantiate a class template, you get all its members, including the private ones that you couldn't otherwise mess with, and which might well not be instantiable for your particular type. Example: https://godbolt.org/z/3hb7Wb3Te
Here's a real-world example from libstdc++: template class std::atomic<void*>; is a hard error. https://godbolt.org/z/eTq9TWqnK