C++ Logo


Advanced search

Re: [SG20] [isocpp-lib-ext] Can't befriend make_unique/make_shared

From: Herb Sutter <hsutter_at_[hidden]>
Date: Wed, 25 Sep 2019 19:17:31 +0000
Actually, the example I gave was:

    class D : public /*possibly transitively*/ B {
        D(const D&); // has all the conveniences, such as =default
        unique_ptr<B> clone() const override { return std::make_unique<D>(*this); } // error

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Sent: Wednesday, September 25, 2019 11:42 AM
To: C++ Library Evolution Working Group <lib-ext_at_[hidden]>; Herb Sutter <hsutter_at_[hidden]>; sg20_at_[hidden]
Subject: Re: [isocpp-lib-ext] Can't befriend make_unique/make_shared

Oh, and one more thing (besides trying this alternative address for SG20's list)—

If such an arcane mechanism were added, and anyone actually used it, I predict that it would be about three months before we saw the first StackOverflow question asking:

"I'm trying to use `class Widget`, which befriends `std::make_shared`. But I actually want to { create `optional<Widget>`, use `Widget` with `boost::shared_ptr`, derive from `Widget` }, and now I'm having difficulty accessing Widget's constructor. How do I get access to the constructor of a `Widget` that tries to lock down access in this way?"

...and then the arms race will start up again in the StackOverflow answers.


On Wed, Sep 25, 2019 at 2:36 PM Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]<mailto:arthur.j.odwyer_at_[hidden]>> wrote:
On Wed, Sep 25, 2019 at 10:56 AM Tony V E via Lib-Ext <lib-ext_at_[hidden]<mailto:lib-ext_at_[hidden]>> wrote:
> On Wed, Sep 25, 2019 at 1:23 AM Matt Calabrese via Lib-Ext <lib-ext_at_[hidden]<mailto:lib-ext_at_[hidden]>> wrote:
>> I agree that this is a problem and does come up sometimes in practice, though I do not believe that creating a solution specifically for make_shared or make_unique is the best option. IMO, this is a small symptom of the overall limited approach to how we do emplace and make operations in C++.
>> [...]
> You could go one step further, and look up the lambda (or "factory") via a trait. So make_unique<Foo>(a,b,c) would call std::construction_trait<Foo>::factory(a,b,c) (which can be a friend of Foo).
> And by default, construction_trait<T>::factory(...) just forwards to the constructor (so current stuff still works), or (equivalently modulo sfinae) make_unique uses if constexpr to check for existence of the factory, calls it, else calls the constructor.

Please, please — do not introduce complexity where no complexity currently exists or is needed!
C++ has a very deserved reputation as an "experts-only language." It is hard to teach. It is hard to use. It is even hard to know what the rules for using it are (because there are so many arcane little rules).

In Herb's case, he's got a student coming to him with a question:

    class C {
        C(int x);
    auto p = std::make_shared<C>(42);

Student: "Why doesn't this compile?"
Teacher: "Ah, it's because `make_shared` can't access your constructor. Make your constructor public."
Student goes off for a while and comes back, having failed to follow the teacher's advice.

    class C {
        C(int x);
        template<class T, class... Ts> friend std::shared_ptr<T> std::make_shared(Ts&&...);
    auto p = std::make_shared<C>(42);

Student: "This still doesn't work!"
Teacher: "You are making the code too complicated. If you want some other library to have access to your constructor, make your constructor public."
Student: "Why doesn't it work, what I wrote?"
Teacher: "The constructor call might be buried several layers of indirection deep, in a helper function."
Teacher: "Besides, even if it weren't, I'm honestly not sure that you got the declaration of `make_shared` correct. The Committee actually has a set of guidelines somewhere saying not to forward-declare functions or templates from `std`, because you can't possibly get them right. What you wrote might work on one implementation and break quietly on another."
Teacher: "Besides, `friend` is a code smell."

If WG21 added a mechanism for code like this to "befriend make_shared" — a necessarily arcane and therefore poorly understood mechanism — well, the student would come back and say something like "Ah, I've found this snippet on StackOverflow. Can you explain to me how it works? Can I use this in my own code?" and we'll have to go through one or two more rounds of this dialogue.

The teacher's answer still won't have changed. You shouldn't do this. `friend` is a code smell. If you want a third party to be able to access your constructors and other methods, make them public. The `private` keyword is specifically for preventing access from third parties such as make_shared.

The fact that your code didn't work on the first try is a teaching opportunity; it gives me the chance to explain about public and private, and helper methods, and the rule about not trying to forward-declare (or inherit from, or befriend) facilities out of `namespace std`.

Complexifying the situation will not help the student, nor the teacher, nor the working programmer (who, having been a student once, instantly knows that the solution is to mark the constructor `public` and move on with his life). Complexifying the situation will harm the former two and likely be ignored by the latter.


Received on 2019-09-25 14:20:44