C++ Logo

std-proposals

Advanced search

Re: [std-proposals] A type trait for detecting virtual base classes

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 2 Oct 2023 10:02:48 -0400
On Mon, Oct 2, 2023 at 1:45 AM Sebastian Wittmeier wrote:

> *Von:* Arthur O‘Dwyer via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mo 02.10.2023 02:55
> On Sun, Oct 1, 2023 at 10:32 AM Giuseppe D'Angelo via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>
> There is an implementation of `is_base_of` in pure C++, courtesy of Boost:
>
> https://www.boost.org/doc/libs/1_83_0/boost/type_traits/is_base_and_derived.hpp
> [...]
>
> Neat! I may try to dissect it later. I notice the big block comment refers
> to some Usenet message-id
> <df893da6.0301280859.522081f7_at_[hidden]>
> but I'm no longer aware of any way to search for that message anywhere on
> the public Internet. If anyone knows how to see what that message was,
> please tell me!
>
> Or could be that df893da6 actually generally refers to posts from Rani
> Sharoni and he mostly posted on *comp.lang.c++.moderated.* BTW I mixed up
> that name in the last message.


But I think the link that you found might be the one to which
Boost.TypeTraits was really referring! (Although it doesn't contain an
"explanation," just a repetition of the code that's now in Boost.)
https://groups.google.com/g/comp.lang.c++.moderated/c/w9iuSAuyqno/m/g71gUU1ObN0J
This goes to a message from Rani Sharoni on 2003-01-28 titled
"SuperSubclass (is_base_and_derived) complete implementation!" with no
replies (at least not in Google's archive/UI).

With that hint (and `git blame`'ing the Boost header's block comment to the
vicinity of February 2003), I was able to find an archive of large swaths
of comp.lang.c++.moderated
<https://archive.org/details/FULL-USENET-BACKUP-2020-Oct-comp.lang.c.moderated.101654.mbox.7z>
on the Internet Archive, and from there find what looks like a very
relevant thread, titled "Is this valid -> part-2."
But that "part 2" thread was started by Dhruv on 2003-12-18.
Unfortunately, that archive goes back only to 2003-07-01 (five months later
than Feb 2003), so it doesn't contain whatever "part 1" was.

Once I had that thread in hand, googling quotations from it found it in
Google's archive after all, here:
https://groups.google.com/g/comp.lang.c++.moderated/c/KK8JLYclDtU/m/QZ1OQjba6z4J
I'll quote it in full, for posterity.

–Arthur

In "Message-ID: <df893da6.0312250556.515613_at_[hidden]>" (Dec 25,
2003), Rani Sharoni wrote:

John Potter <jpotter_at_[hidden]> wrote in message

news:<qdv9uvkntcp0jm7ps1vgk0qv7h1ej1ep5g_at_[hidden]>...

> On 20 Dec 2003 20:26:15 -0500, "Dhruv" <dhruvbird_at_[hidden]> wrote:

> > Is there some sort of rule that if you have 2 identical functions

> > overloaded by const qualification ONLY, then the const version will be

> > called ONLY for Const objects?

>

> I think you may still be confusing things.

>

> operator T& (); // called only on non-const objects

> operator T const& () const; // called only on const objects

>

> It is the const on the end that makes the overload work. There is a

> better match for the non-const function on non-const objects because

> the other one requires a const qualification conversion. Yes, the

> const function could be called on a non-const object, but not when

> the non-const member is available.

>

> If you remove the rightmost const or add one to the other function

> then creation on an int& will use the operator int& and creation of

> an int const& will be ambiguous.


This issue is a bit more complicated.


Consider the following case:

struct A1 {

    operator char const*();

private: // investigation ;-)

    operator char*();

};


char const* p1 = A1(); // #1


In this case there is no ambiguity and #1 is well-formed.

There are two conversion sequences (CS) to consider:

S1: A -> char* -> char const*

S2: A -> char const*


According to 13.3.3/1/6, A::operator const char* is better candidate

since the standard CS const char* (i.e. identity) is proper

subsequence of char* -> char const* (i.e. qualification adjustment)

and therefore is better per 13.3.3.2/3/1/2 (i.e. the proper

subsequence rule)


The original reference case is surprisingly different since according

to TC1 DR #59:

"Conversion functions that return "reference to cv2 X" return lvalues

of type "cv2 X" and are therefore considered to yield X for this

process of selecting candidate functions"


For example:

struct A2 {

    operator char const&();

    operator char&();

};


char const& r1 = A2(); // Ambiguous user-defined-conversion


Both conversion functions are viable and for ranking considerations

returns the same type (i.e. char).


There is an open DR (#233) about this inconsistency which seems to

violate the proper subsequence rule. The DR also mentioned the case

where inheritance is involved:

struct A {};

struct B : A {};


struct X {

   operator A&();

private:

   operator B&();

};


A& g = X(); // #2


The DR state that #2 is ambiguous yet all the compilers I tried think

that #2 is well-formed. Maybe the proper subsequence rule comes to

play in this case.


Now for the original quirky case:

struct A3 {

    operator char&();

private:

    operator char const&() const;

};


char const& r1 = A3(); // #3


The reason that #3 is well-formed is probably one of the most obscure

cases in the standard. There are two *viable* conversion sequences to

consider:

S1: A -> A& -> (UDC) char& -> char const&

S2: A -> A const& -> (UDC) char const&


>From the first look it seems that the proper subsequence rule doesn't

apply in this case since S1 is obviously not proper subsequence of S2

yet overloading thinks that S1 is better.

Similar case was presented one year ago in this NG

(http://tinyurl.com/502f) and I remember that Daveed Vandevoorde had

to ask Steve Adamczyk for the answer which means that the explanation

is far from being obvious (i.e known by few overload-resolution gurus

in the committee).


Eventually it boils down to 13.3.3.1/4 that yields that in the above

case the conversion is considered up to the UDC. This means that the

candidates CS are actually:

S1': A -> A&

S2': A -> A const&


S1' is obviously better than S2' per 13.3.3.2/3/1/4 which is related

to the proper subsequence rule.


>From the same reasoning the following is also well-formed:

struct A4 {

    operator char*();

private:

    operator void const*() const;

};


void const* p = A4(); // #5


After two months I realized that this obscure property (i.e.

13.3.3.1/4) can be exploited in order to implement one of the most

surprising type traits – is_base_and_derived that handles multiple and

private bases. http://tinyurl.com/2bpo2.


The final is_base_and_derived implementation contains fine explanation

by Terje Slettebo:

http://www.boost.org/boost/type_traits/is_base_and_derived.hpp


Regards,

Rani

Received on 2023-10-02 14:03:03