C++ Logo

std-proposals

Advanced search

Re: Idea: "friend" scope

From: Matthew Woehlke <mwoehlke.floss_at_[hidden]>
Date: Fri, 21 Jun 2019 11:32:20 -0400
On 20/06/2019 18.59, Arthur O'Dwyer via Std-Proposals wrote:
> If you're allowed to rewrite the implementation of `create`, then you could
> just pull the construction up to `create`'s level:
>
> static std::shared_ptr<a> create(int x) {
> return std::shared_ptr<a>(new a(x));
> }

...which, as noted, loses all the advantages of make_shared. (Note that
there is an exception safety advantage as well, even if it's extremely
unlikely to be relevant in practice.)

> Or if you must use `make_shared` for its memory-footprint advantages, you
> can do it via some insanity like:
> https://godbolt.org/z/msXcjJ
>
> static std::shared_ptr<a> create(int x) {
> struct Wrapper {
> int x;
> operator a() const { return a(x); }
> };
> return std::make_shared<a>(Wrapper{x});
> }

There's enough assembly being generated that it's hard to be certain,
but offhand, it seems that this generates worse code? (It certainly
generates *more*: https://godbolt.org/z/lGaz4j.)

> Regardless, I don't see how your idea about "friend scope" would help at
> all. Your whole problem is that the access-to-protected-constructor happens
> in a *non-friend* scope, buried deep inside `make_shared`. Adding a
> "friend scope" somewhere inside A::create won't help a scope inside
> `make_shared` access protected members.

...then you misunderstood the idea. The scope applies to *all code*,
including recursively called code, within the scope. (Well, sort of;
back to this in a second.) What makes it "out there" is that this means
that the access protection depends on the scope for which a function was
called, which is... unusual, to say the least, for C++.

Obviously this only works if the compiler is able to "see into" the code
being called; so, practically speaking, it only extends as far as code
that the compiler could potentially inline. For make_shared, this works,
because almost every part of that call is necessarily templated.

Consider:

  extern void bar();
  void a::foo() friend
  {
    std::make_shared<a>(...); // friend of 'a'
    bar(); // not a friend; definition is not visible to compiler
  }

The reason I think this *may* be plausible is that compilers generate a
call stack when an access protection violation occurs. For example:

  in <implementation details>
  required by <more implementation details>
  ...
  required by `std::make_shared<a>(T...)` (with `T...` = `{int}`)
  error: `a::a(int)` is protected

So the way this feature would be implemented is, whenever the compiler
sees that an access protection violation has occurred, it first looks at
its known call stack, and, if a 'friend scope' is encountered for the
class in question, "ignores" the error and continues compiling the code.

-- 
Matthew

Received on 2019-06-21 10:34:12