struct C
{
virtual void f();
};
C c;
c.f(); // the call likely will be devirtualized
f(c);
void f(C &c)
{
c.f(); // the call unlikely will be devirtualized
}
void f(C &c);
C & final cref
void f(C & final c)
{
c.f(); // the call can be devirtualized again
}
C c;
f(c); // OK
class D : public C {};
D d;
f(d); // ERROR
Possible solution.
Add new qualifier for references:
C & final cref
Binds only to objects of exactly type C (but not to references).
void f(C & final c)
{
c.f(); // the call can be devirtualized again
}
C c;
f(c); // OK
class D : public C {};
D d;
f(d); // ERROR
C& c2 = d;
f(c2); // This should be forbidden
C final& c3 = c; // A final reference
f(c3); // OK
I see some problems:
- final is a contextual keyword, with your proposed syntax, it's hard to keep it contextual.
- You should lift the restriction to not accept normal references, e.g:
auto pc = new C();
f(*pc);
`new` expressions would return `T exact*`, which would naturally decay to a `T*`, but could be stored in a `T exact*t` that preserves the fact that it is exactly what it says. Performing `&` on a variable of type `T` (unless it overrides it) will return a `T exact*` as well. Variables of type `T` will preferentially bind to `T exact&` before `T&`, through overload resolution mechanics. Something similar goes for prvalues. `any_cast`, by virtue of how it works, should always return `exact` pointers (it doesn't return references at all).
It would also be good to have `dynamic_cast` be able to safely convert a pointer/reference to an `exact` pointer/reference, returning nullptr/throwing if the specified `T exact*/&` is not the dynamic type of the object.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/95f1af2b-7947-37dd-fef9-4cc4c09a8472%40gmail.com.
`new` expressions would return `T exact*`, which would naturally decay to a `T*`, but could be stored in a `T exact*t` that preserves the fact that it is exactly what it says. Performing `&` on a variable of type `T` (unless it overrides it) will return a `T exact*` as well. Variables of type `T` will preferentially bind to `T exact&` before `T&`, through overload resolution mechanics. Something similar goes for prvalues. `any_cast`, by virtue of how it works, should always return `exact` pointers (it doesn't return references at all).
It would also be good to have `dynamic_cast` be able to safely convert a pointer/reference to an `exact` pointer/reference, returning nullptr/throwing if the specified `T exact*/&` is not the dynamic type of the object.
Won't this break existing code by messing with type deduction?
Is this really a problem that needs solving? Good class hierarchy design and appropriate use of `final` should be sufficient to avoid unnecessary virtual function calls.
On 28.02.2017 14:53, Victor Dyachenko wrote:
>
> *Possible solution.*
>
> Add new qualifier for references:
>
> |
> C &finalcref
> |
>
> Binds only to objects of exactly type C (but not to references).
>
> |
> voidf(C &finalc)
> {
> c.f();// the call can be devirtualized again
> }
>
> C c;
> f(c);// OK
>
> classD :publicC {};
> D d;
> f(d);// ERROR
> |
>
Note that this part of the problem can easily be solved with a library
feature:
D d;
final_ref<C> rt(static_cast<C&>(d));
C c;
final_ref<C> r(c);
r.f(); // can virtual call be avoided here? how?
D d;
C &cr = d; // OK
final_ref<C> r(cr); // Ooops
To deal with all of these cases, we need something stronger. We need to recognize that pointers and references equally want to participate in this sort of thing, which I will refer to as `exact` rather than `final`.
An `exact` pointer/reference can be converted into a non-`exact` pointer/reference, but the reverse is not true. And in cases where the compiler cannot statically detect the `exact`ness of a pointer/reference, users need the equivalent of a `const_cast` for it.
Em terça-feira, 28 de fevereiro de 2017, às 05:53:02 PST, Victor Dyachenko
escreveu:
> I'd like to discuss the following problem. In C++ reference of type T can
> refer to any type derived from T as well as to T itself. It creates some
> issues.
In my opinion, non-issues.
> 1) Suppression of devirtualization.
>
> struct C
> {
> virtual void f();
> };
>
> C c;
> c.f(); // the call likely will be devirtualized
> f(c);
>
> void f(C &c)
> {
> c.f(); // the call unlikely will be devirtualized
> }
>
> Sometimes we don't need polymorphic behavior, just want to pass the
> reference to concrete object (function decomposition).
If this function f *knows* beyond the shadow of a doubt that it doesn't need
the virtual call, you can write;
c.C::f();
I'd like to discuss the following problem. In C++ reference of type T can refer to any type derived from T as well as to T itself. It creates some issues.
1) Suppression of devirtualization.
struct C
{
virtual void f();
};
C c;
c.f(); // the call likely will be devirtualized
f(c);
void f(C &c)
{
c.f(); // the call unlikely will be devirtualized
}
Sometimes we don't need polymorphic behavior, just want to pass the reference to concrete object (function decomposition).
2) Object slicing.
void f(C &c);
Want only objects of exactly class C to be accepted (no derived) but don't want to copy the object.
On Tuesday, February 28, 2017 at 11:25:24 AM UTC-5, joseph....@gmail.com wrote:`new` expressions would return `T exact*`, which would naturally decay to a `T*`, but could be stored in a `T exact*t` that preserves the fact that it is exactly what it says. Performing `&` on a variable of type `T` (unless it overrides it) will return a `T exact*` as well. Variables of type `T` will preferentially bind to `T exact&` before `T&`, through overload resolution mechanics. Something similar goes for prvalues. `any_cast`, by virtue of how it works, should always return `exact` pointers (it doesn't return references at all).
It would also be good to have `dynamic_cast` be able to safely convert a pointer/reference to an `exact` pointer/reference, returning nullptr/throwing if the specified `T exact*/&` is not the dynamic type of the object.
Won't this break existing code by messing with type deduction?
I don't know; would it?
It seems to me that, so long as `T exact */&` can be used in exactly the same places as `T */&`, what would it matter if a template deduces something as a `T exact &` instead of a `T&`?
Why? Can you give a use-case that doesn't involve copying?
On Tuesday, February 28, 2017 at 10:57:46 PM UTC+3, Thiago Macieira wrote:Why? Can you give a use-case that doesn't involve copying?
When I need to decompose my function ("Extract Function"). I don't want to copy all my big complex objects every time.
JavaLikeTreeMap map; // derived from AbstractMap
size_t n = 0;
for(auto &en : map) n++; // working with concrete class
size_t count(const JavaLikeTreeMap &map)
{
size_t n = 0;
for(auto &en : map) n++; // looks same but it can be any derived class also
return n;
}
JavaLikeTreeMap map; // derived from AbstractMap
size_t n = 0;
for(auto &en : map) n++; // working with concrete class
size_t count(const JavaLikeTreeMap &map)
{
size_t n = 0;
for(auto &en : map) n++; // looks same but it can be any derived class also
return n;
}
Em quarta-feira, 1 de março de 2017, às 00:00:49 PST, Victor Dyachenko
escreveu:
> When I need to decompose my function ("Extract Function"). I don't want to
> copy all my big complex objects every time.
Explain what you mean by that. The only thing that comes to mind in "extract
function" is reading the virtual table to get the actual function pointer, as
opposed to a PMF that goes again through the virtual table. That is not
permitted in C++.
I know what it does. I am asking why you would want such a function *not* to
work for derived classes. That is not explained.
On Wed, Mar 1, 2017 at 12:07 AM, Victor Dyachenko <victor.d...@gmail.com> wrote:
JavaLikeTreeMap map; // derived from AbstractMap
size_t n = 0;
for(auto &en : map) n++; // working with concrete class
size_t count(const JavaLikeTreeMap &map)
{
size_t n = 0;
for(auto &en : map) n++; // looks same but it can be any derived class also
return n;
}I think the problem here is that you're working against the type system. Since JavaLikeTreeMap is polymorphic, this function obeys all the usual OOP principles, such as the Liskov Substitution Principle; so the signature of this function expresses the idea that you can "count" the elements of any object that IS-A JavaLikeTreeMap.
On Wednesday, March 1, 2017 at 10:49:11 PM UTC+3, Arthur O'Dwyer wrote:On Wed, Mar 1, 2017 at 12:07 AM, Victor Dyachenko <victor.d...@gmail.com> wrote:
JavaLikeTreeMap map; // derived from AbstractMap
size_t n = 0;
for(auto &en : map) n++; // working with concrete class
size_t count(const JavaLikeTreeMap &map)
{
size_t n = 0;
for(auto &en : map) n++; // looks same but it can be any derived class also
return n;
}I think the problem here is that you're working against the type system. Since JavaLikeTreeMap is polymorphic, this function obeys all the usual OOP principles, such as the Liskov Substitution Principle; so the signature of this function expresses the idea that you can "count" the elements of any object that IS-A JavaLikeTreeMap.Am I working against type system in the first case? When I create concrete object and call virtual function directly w/o reference. Really? Don't think so. And I want just to wrap the same code into function without changing the way that virtual function is called.
When I'm writing the code I have a choice: write such code inline or create a function and slightly pessimize performance.
...Or mark the troublesome methods final, or write the code with explicit obj.C::method() qualification to turn off virtual dispatch, or not make the troublesome methods virtual in the first place, or use a more aggressive devirtualizer if you can find one, or probably some other things.
struct ConcreteMap final : public JavaLikeTreeMap
{
using JavaLikeTreeMap::JavaLikeTreeMap
};
size_t count(const ConcreteMap &map)
{
...
}