Non-polymorphic references

160 views
Skip to first unread message

Victor Dyachenko

unread,
Feb 28, 2017, 8:53:02 AM2/28/17
to ISO C++ Standard - Future Proposals
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.

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

TONGARI J

unread,
Feb 28, 2017, 9:28:08 AM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 9:53:02 PM UTC+8, Victor Dyachenko wrote:
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

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:
    C& c2 = d;
    f
    (c2); // This should be forbidden
    C
    final& c3 = c; // A final reference
    f
    (c3); // OK

Victor Dyachenko

unread,
Feb 28, 2017, 9:32:09 AM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 5:28:08 PM UTC+3, TONGARI J wrote:
I see some problems:
  • final is a contextual keyword, with your proposed syntax, it's hard to keep it contextual.
 Notation may be different. I don't mind.
  • You should lift the restriction to not accept normal references, e.g:
"Binds only to objects of exactly type C (but not to references)"

TONGARI J

unread,
Feb 28, 2017, 10:10:27 AM2/28/17
to ISO C++ Standard - Future Proposals
Somehow my eyes didn't work correctly :p
Anyway, you should add it to your example. 

Nicol Bolas

unread,
Feb 28, 2017, 11:00:07 AM2/28/17
to ISO C++ Standard - Future Proposals
If we're going to explore this devirutalization problem, then I think it is important to be able to explore the full breadth of the problem.

For example, this proposed idea works fine for cases where it is statically impossible for the type to be the wrong type. But

This is a problem that ought to work just as well for pointers as references:

auto pc = new C();
f
(*pc);

The compiler can clearly see that `pc` has `C` as its dynamic type. Yet this example isn't supported by your idea (as I understand it).

Also, what about cases where a pointer has to pass a pointer through a `void*` or an `any`? Such objects have a dynamic type, and the user knows exactly what that type is (otherwise they couldn't reconstitute it). But the compiler cannot make that determination?

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.

When you add all of this up, what do you get? A third cv-qualifier. Though it is a bit backwards from normal cv-qualfiiers. You can freely add `const` to a pointer/reference, but you cannot remove `const` without an explicit cast. Here, we need the opposite: we should be able to freely remove `exact` from a pointer/reference, but you cannot add it back without an explicit cast. So unlike the others, the qualified version is the more accepting version rather than the more restricted one.

`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.

My biggest concern with such a feature is that it will encourage proliferation of `exact` qualifications on function parameters, even though most of them will not even be polymorphic types.

joseph....@gmail.com

unread,
Feb 28, 2017, 11:25:24 AM2/28/17
to ISO C++ Standard - Future Proposals
`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.

Jonathan Müller

unread,
Feb 28, 2017, 11:27:50 AM2/28/17
to std-pr...@isocpp.org
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:

|
template <typename T>
class final_ref
{
public:
final_ref(T& ref);

template <typename U>
final_ref(U&) = delete;


};
|

Michał Dominiak

unread,
Feb 28, 2017, 11:33:16 AM2/28/17
to std-pr...@isocpp.org
This also doesn't work for the case of `f(*pointer_to_foo)`.
 
--
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.

Nicol Bolas

unread,
Feb 28, 2017, 11:34:18 AM2/28/17
to ISO C++ Standard - Future Proposals, joseph....@gmail.com
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&`?

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.

I'm just going along with the OP's idea. I'm not really sure what the use cases are for this sort of thing. I mean yes, the OP showed an example, but does it happen often enough that it really needs a language feature?

Also, the complexity I'm suggesting that gets added to the OP's simple idea is actually rather necessary. Not just to handle pointers, but to handle things like forwarding. After all, if I pass an exact reference to a function that takes a `T&&`, then clearly the exact-ness of that reference needs to be preserved. And it can only do that if it's a first-class part of the type system, such that templates deduce an exact reference when you provide one. Just as templates deduce a `const` reference when you provide one.

Nicol Bolas

unread,
Feb 28, 2017, 11:42:00 AM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 11:27:50 AM UTC-5, Jonathan Müller wrote:
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:

The goal of this idea is to enforce devirtualization, and adding a user-defined type (or even a standard library type) won't enforce devirtualization. Even if you make it a standard library type, it cannot enforce anything, because this is still legal C++:

D d;
final_ref
<C> rt(static_cast<C&>(d));

The compiler cannot assume that the pointer/reference stored in `final_ref` is of the dynamic type `T`. And without that assumption, the compiler cannot devirtualize anything.

I suppose, as a standard library type, you could declare by fiat that the compiler shall assume that its `operator->` and `operator*` overloads shall return a pointer/reference to an object who's dynamic type is `T`. And that if the dynamic type of the `T&` you provide to the constructor is not `T` itself, then UB results. But that would be exceedingly strange behavior for a standard library type.

It also puts everything into runtime checks, rather than compile-time assurances.

Victor Dyachenko

unread,
Feb 28, 2017, 2:04:47 PM2/28/17
to ISO C++ Standard - Future Proposals

And how does it solve the problem with virtual calls?

C c;
final_ref
<C> r(c);
r
.f(); // can virtual call be avoided here? how?

It even doesn't solve slicing problem (in non-trivial scenario):

D d;
C
&cr = d; // OK
final_ref
<C> r(cr); // Ooops

Victor Dyachenko

unread,
Feb 28, 2017, 2:17:47 PM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 7:00:07 PM UTC+3, Nicol Bolas wrote:

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`.

Agree, it may be more precise terminology. But C++ tries hard to avoid introducing new keywords. The notation may be changed (may be even without introducing any words). I want to discuss the behaviour  in the first place.
 
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.

Sure, some mechanism like const_cast is required to be able to tell the compiler "I promise, this can be done safely".

Thiago Macieira

unread,
Feb 28, 2017, 2:57:46 PM2/28/17
to std-pr...@isocpp.org
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();

This work with templates too:

template <typename T>
void f(const T &t)
{
t.T::f();
}

> 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.

Why? Can you give a use-case that doesn't involve copying?

Even for copying, I'd consider it a non-issue since a polymorphic class not be
sliced should have deleted its copy operators.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Nicol Bolas

unread,
Feb 28, 2017, 3:21:47 PM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 2:57:46 PM UTC-5, Thiago Macieira wrote:
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 think a big part of the point of the feature is to explicitly prevent you from calling it with the wrong type. Admittedly, I'm not sure precisely why that's necessary, but it does have the side benefit of making it so that you don't have to write a lot of redundant `Typename::` before all of your function calls if you want to devirtualize them.

Arthur O'Dwyer

unread,
Feb 28, 2017, 7:38:28 PM2/28/17
to ISO C++ Standard - Future Proposals
On Tuesday, February 28, 2017 at 5:53:02 AM UTC-8, Victor Dyachenko wrote:
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.

I imagine solving this via a tag template similar to not_null<T>; let's call it not_polymorphic<C>.
The idea is that you're willing to take the hit of a runtime check the first time you put a value into the "not_null" or "not_polymorphic" system; but then once it's in there, you can pass it around and access its value without needing to repeat the runtime check, because you're guaranteed that in order for one of these objects to exist, the original creator must have checked for "nullness" or "polymorphicness" already.

#include <typeinfo>

#define ASSUME(condition) do { if (!(condition)) __builtin_unreachable(); } while (0)
#define ASSUME_DYNAMIC_TYPE(obj, type) ASSUME(typeid(obj) == typeid(type))

template<class C>
class not_polymorphic {
    C *ptr;
  public:
    explicit not_polymorphic(C& c) : ptr(&c) { assert(typeid(c) == typeid(C)); }
    C& get() const { ASSUME_DYNAMIC_TYPE(*ptr, C); return *ptr; }
    operator C& () const { ASSUME_DYNAMIC_TYPE(*ptr, C); return *ptr; }
};

Then all that is required is that your compiler of choice be smart enough to devirtualize calls when the typeid of the object is known ahead of time.
It appears to me that Clang is not yet this smart, and I assume GCC isn't either; but if this idiom became common, they might start supporting it.

This is basically Nicol's "exact type qualifier" idea from a few posts down, except that I'm doing it within the existing type system. The explicit constructor above is the equivalent of Nicol's "something like const_cast", and the implicit conversion operator is the equivalent of Nicol's implicit conversion from T exact& to T&.

Personally, I just don't use polymorphism. Unhelpful, I know. ;)

–Arthur

Joseph Thomson

unread,
Feb 28, 2017, 8:37:58 PM2/28/17
to ISO C++ Standard - Future Proposals
On Wed, Mar 1, 2017 at 12:34 AM, Nicol Bolas <jmck...@gmail.com> wrote:

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&`?

Well, `is_same_v<decltype(new T), T*>` will become false, unless `is_same` were to recursively ignore the `exact` qualifier, in which case in what sense is it a part of the type system?

Actually, that raises a question. What does it mean to be of type `T exact`? Does the `exact` qualifier only apply to references and pointers?

p.s. Sorry Nicol, I originally forgot to "Reply to all".

Magnus Fromreide

unread,
Mar 1, 2017, 1:25:40 AM3/1/17
to std-pr...@isocpp.org, joseph....@gmail.com
On Tue, Feb 28, 2017 at 08:34:18AM -0800, Nicol Bolas wrote:
> 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&`?

What would happen if there is a (partial) specialization for T&?

/MF

Victor Dyachenko

unread,
Mar 1, 2017, 3:00:49 AM3/1/17
to ISO C++ Standard - Future Proposals
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.

And see P0221R0: Proposed wording for default comparisons, revision 2 about slicing problems.

Victor Dyachenko

unread,
Mar 1, 2017, 3:07:46 AM3/1/17
to ISO C++ Standard - Future Proposals
On Wednesday, March 1, 2017 at 11:00:49 AM UTC+3, Victor Dyachenko wrote:
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;
}

And I cannot use copy at all if my object needs to be modified by the function (non-const reference)

Arthur O'Dwyer

unread,
Mar 1, 2017, 2:49:11 PM3/1/17
to ISO C++ Standard - Future Proposals
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.

I suppose this is a decent argument for the "final" qualifier on methods (if you use a lot of polymorphism in speed-critical sections, which I still think is a bad idea, honestly). Qualifying JavaLikeTreeMap::begin() and JavaLikeTreeMap::end() as "final" will solve your problem while remaining within the type system.

The way "final" affects things is by saying: "I, Victor, promise to you, the compiler, that if some object O IS-A JavaLikeTreeMap, then its begin() method acts 100% like this, and its end() method acts 100% like this." So the compiler becomes free to inline those methods.

Now, you might still claim there's a problem. What if JavaLikeTreeMap::begin() cannot be made "final", because you actually do want to create a child class

    class JavaLikeTreeMapWithKnobsOn : JavaLikeTreeMap
    {
        auto begin() const override;
        auto end() const override;
    };
    JavaLikeTreeMapWithKnobsOn jltmwko;

? Well, in that case, you've just created an object jltmwko which IS-A JavaLikeTreeMap according to the type system, but whose elements cannot be counted using your (devirtualized, inlined, non-polymorphic) "count" function. So you've broken the Liskov substitution rule, so IMO you shouldn't expect the compiler to help you out at all.

–Arthur

Anthony Hall

unread,
Mar 1, 2017, 4:13:45 PM3/1/17
to ISO C++ Standard - Future Proposals
In a similar spirit to Arthur's not_polymorphic<> tag type idea: at least as far as the efficiency goals of enabling de-virtualization in more contexts than compilers would ordinarily be able to, this sounds like one of the poster child use cases for custom attributes: an optimization hint to the compiler, where otherwise identical functionality would result.  Part of the contract with the compiler could be that, given the right flags, the compiler would agree to return warnings or errors, so that the desirable compile-time checks could be given.  The syntax of attributes might be a little bit ugly or unwieldy, in the position of cv-qualifiers (and I'm not sure off-hand: do the current positioning rules for custom attributes allow for cv-qualifier-like use?).  But I wonder if it could afford all the benefits desired, without having to bikeshed a new keyword or syntax for it, and without having to wait for standardization, so long as compilers are willing to recognize it.

Thiago Macieira

unread,
Mar 2, 2017, 1:51:15 AM3/2/17
to std-pr...@isocpp.org
Em quarta-feira, 1 de março de 2017, às 00:07:46 PST, Victor Dyachenko
escreveu:
> 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 know what it does. I am asking why you would want such a function *not* to
work for derived classes. That is not explained.

> And I cannot use copy at all if my object needs to be modified by the
> function (non-const reference)

If your object is copyable (not virtual-clone()able) , it shouldn't be
polymorphic. That's an API design problem, so in my opinion, a non-issue.

Thiago Macieira

unread,
Mar 2, 2017, 1:52:15 AM3/2/17
to std-pr...@isocpp.org
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++.

Victor Dyachenko

unread,
Mar 2, 2017, 2:04:51 AM3/2/17
to ISO C++ Standard - Future Proposals
On Thursday, March 2, 2017 at 9:52:15 AM UTC+3, Thiago Macieira wrote:
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 mean function decomposition. Parts of a big function (chunks of code) become functions itself.


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.
Generated code. It will be more efficient.

Victor Dyachenko

unread,
Mar 2, 2017, 2:16:23 AM3/2/17
to ISO C++ Standard - Future Proposals
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.

Arthur O'Dwyer

unread,
Mar 2, 2017, 2:38:41 AM3/2/17
to ISO C++ Standard - Future Proposals
On Wed, Mar 1, 2017 at 11:16 PM, Victor Dyachenko <victor.d...@gmail.com> wrote:
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.

In classical-OOP-land, creating a function (an addition to the public interface) is not a trivial refactor, because it fundamentally creates a new operation that you can do with certain kinds of objects (and not do with other kinds of objects). In classical OOP, the only means we have for describing "kinds of objects" is the class hierarchy, which brings with it all the classical rules such as the Liskov Substitution Principle.

Now, if you can introduce that new function without increasing the surface area of your interface, then you'll do fine. For example, in C++ you could make your helper function file-scope-static, so that nobody would be allowed to call it from any other translation unit, and the compiler might notice that you only ever call it from one place or with objects of one dynamic type, and do the inlining and devirtualization for you.

But if you create a new externally visible method (or, in C++, a new externally visible free function), then you are expanding the surface area of your class type, and it makes sense that substitutability is preserved.

 
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.

–Arthur

Victor Dyachenko

unread,
Mar 2, 2017, 2:51:18 AM3/2/17
to ISO C++ Standard - Future Proposals
On Thursday, March 2, 2017 at 10:38:41 AM UTC+3, Arthur O'Dwyer wrote:

...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.

final is not a solution. It requires an ability to modify used class. Not all classes I use are written by me, I use libraries too ;-)

Thiago Macieira

unread,
Mar 2, 2017, 3:02:16 AM3/2/17
to std-pr...@isocpp.org
Em quarta-feira, 1 de março de 2017, às 23:04:50 PST, Victor Dyachenko
escreveu:
> I mean function decomposition. Parts of a big function (chunks of code)
> become functions itself.
>
> 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.
>
> Generated code. It will be more efficient.

Sorry, you're still not explaining. You're handwaving.

Please give a concrete use-case.

Victor Dyachenko

unread,
Mar 2, 2017, 3:22:45 AM3/2/17
to ISO C++ Standard - Future Proposals

Hm... Actually we can create a wrapper for polymorphic class:

struct ConcreteMap final : public JavaLikeTreeMap
{
   
using JavaLikeTreeMap::JavaLikeTreeMap
};

size_t count
(const ConcreteMap &map)
{
   
...
}

Reply all
Reply to author
Forward
0 new messages