Date: Fri, 21 Jun 2019 12:16:31 -0400
On Fri, Jun 21, 2019 at 11:32 AM Matthew Woehlke <mwoehlke.floss_at_[hidden]>
wrote:
> On 20/06/2019 18.59, Arthur O'Dwyer via Std-Proposals wrote:
> > If you're allowed to rewrite the implementation of `create`, then [...]
> 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.)
>
No, the codegen is identical.
https://godbolt.org/z/tVI70Q
Remember that when comparing codegen, you have to use the same optimization
level and the same compiler. On Godbolt, you also have to make sure that
the same checkboxes are ticked in both panes.
> > 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;
>
Well, not! You're trying to use the word "scope" to mean something other
than "scope". In C++, "scope" means, like, a lexical scope: the scope
between a left-brace and a right-brace. What you're describing is a way to
*dynamically* turn off access control, so that for example `foo()` compiles
when called from `baz()`, but doesn't compile when called from `bar()`.
class B { int m; };
B b;
void foo() { b.m = 42; }
void bar() { foo(); }
void baz() { friend { foo(); } }
That's not something that can *ever* work, with separate compilation. We
can't have two different versions of the code for `foo` floating around out
there. (And yes, you can SFINAE on the ability to access a member.)
[...]
> 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
>
That's not a *call *stack; there is no implication that std::make_shared<a>
*calls* <more implementation details>.
What it is is an *instantiation *stack. Instantiating make_shared<a>
requires the compiler to *instantiate *<more implementation details>. Maybe
it's getting instantiated so that we can call it at runtime; maybe it's
getting instantiated so we can take its address; maybe it's getting
instantiated so we can take its decltype() and for no other reason.
Anyway, given that the codegen for `Wrapper<a>` is identical, is this all a
moot point now?
HTH,
Arthur
wrote:
> On 20/06/2019 18.59, Arthur O'Dwyer via Std-Proposals wrote:
> > If you're allowed to rewrite the implementation of `create`, then [...]
> 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.)
>
No, the codegen is identical.
https://godbolt.org/z/tVI70Q
Remember that when comparing codegen, you have to use the same optimization
level and the same compiler. On Godbolt, you also have to make sure that
the same checkboxes are ticked in both panes.
> > 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;
>
Well, not! You're trying to use the word "scope" to mean something other
than "scope". In C++, "scope" means, like, a lexical scope: the scope
between a left-brace and a right-brace. What you're describing is a way to
*dynamically* turn off access control, so that for example `foo()` compiles
when called from `baz()`, but doesn't compile when called from `bar()`.
class B { int m; };
B b;
void foo() { b.m = 42; }
void bar() { foo(); }
void baz() { friend { foo(); } }
That's not something that can *ever* work, with separate compilation. We
can't have two different versions of the code for `foo` floating around out
there. (And yes, you can SFINAE on the ability to access a member.)
[...]
> 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
>
That's not a *call *stack; there is no implication that std::make_shared<a>
*calls* <more implementation details>.
What it is is an *instantiation *stack. Instantiating make_shared<a>
requires the compiler to *instantiate *<more implementation details>. Maybe
it's getting instantiated so that we can call it at runtime; maybe it's
getting instantiated so we can take its address; maybe it's getting
instantiated so we can take its decltype() and for no other reason.
Anyway, given that the codegen for `Wrapper<a>` is identical, is this all a
moot point now?
HTH,
Arthur
Received on 2019-06-21 11:18:31