关于重载决议:boost::is_base_and_derived和Move Constructors中的手法

Generic<Programming>: Move Constructors by Andrei Alexandrescu

see source code

。。。 。。。

Now in starting to overload Connect, an idea would be to define Connect(const String&) to catch "genuine" constant objects. This, however, would be a mistake because this declaration will "eat" all String objects — be they lvalues or temporaries. So the first good idea is to not declare a function that accepts a const reference, because it swallows all objects like a black hole.

A second try is to define Connect(String&) in an attempt to catch non-const lvalues. This works well, and in particular const values and unnamed temporaries can’t be "eaten" by this overload — a good start. Now we only have to differentiate between const objects and non-const temporaries.

To do this, the technique we apply is to define two "type sugar" classes ConstantString and TemporaryString, and to define conversion operators from String to those objects:

class String;


// "type sugar" for constant Strings
struct ConstantString
{
const String obj;
};


// "type sugar" for temporary Strings
// (explanation coming)
struct TemporaryString : public ConstantString {};


class String
{
public:
… constructors, destructors,
operations, you name it …
operator ConstantString() const
{
ConstantString result;
result.obj = this;
return result;
}
operator TemporaryString()
{
TemporaryString result;
result.obj_ = this;
return result;
}
};

So now String defines two conversion operators. One notable difference between them is that TemporaryString doesn’t apply to const String objects.

Now say you define the following three overloads:

// binds to non-const temporaries
void Connect(TemporaryString);
// binds to all const objects (lvalues AND temporaries)
void Connect(ConstantString);
// binds to non-const lvalues
void Connect(String& str)
{
// just forward to the other overload
// since ContantString has no constructor taking a String as parameter,
// two convertion route exist:
// str —> str const —> ConstantString
// str —> TemporaryString —> ConstantString // #1
//
Connect(ConstantString(str));
}

Here’s how it all works. Constant String objects are "attracted" by Connect(ConstantString). There is no other binding that could work; the other two work for non-const Strings only.

Temporary objects can’t go to Connect(String&). They could, however, go to either Connect(TemporaryString) or Connect(ConstantString), and the former overload must be chosen unambiguously. That’s the reason for deriving TemporaryString from ConstantString, a trick that deserves some attention.

Consider for a moment that ConstantString and TemporaryString were totally independent types. Then, when prompted to copy a temporary object, the compiler would be equally motivated to go either:

operator TemporaryY() —> Y(TemporaryY)

or:

operator ConstantY() const —> Y(ConstantY)

Why the equal motivation? This is because the non-const to const conversion is "frictionless" as far as selecting member functions is concerned. (两个不同参数的函数,两个用户自定义转换,因此两个转换一样好,see C++ primer ———–> this is not the case!!! see the following)

【explanation:
for void Connect(ConstantString);
Y —> Y const —> Constant (UDC)
Y —> Temporary (UDC) —> Constant
for void Connect(TemporaryString);
Y —> Temporary (UDC)

for the first function, choose the second conversion, for the second, there is only one choice.
finally the last conversion is chosen obviously.

//Testing source code:
class String;
struct ConstantString
{
    const String obj_;
};

struct TemporaryString : public ConstantString
{
};

class String
{
public:
    operator ConstantString() const
    {
        cout << "operator ConstantString() const" << endl;
    }
    operator TemporaryString()
    {
        cout << "operator TemporaryString()" << endl;
    }
};

void test(ConstantString s){}

int main()
{
    test(String());
    return 0;
}

output : operator TemporaryString()

【 note
// compiled by g++-3.4.4 & VC8.0
struct Class
{
    Class(){}
    operator int() const
    {
        cout << "int() const" << endl;
        return 0;
    }
    operator int()
    {
        cout << "int()" << endl;
        return 0;
    }
};
int main()
{
    Class a;
    int i = Class();   // output "int()", so by default a r-value temparory is not treated as const
}

The need, therefore, is to give the compiler more "motivation" to choose the first route than the second. That’s where the inheritance kicks in. Now the compiler says: "Ok, I guess I could go through ConstantString or TemporaryString… but wait, the derived class TemporaryString is a better match!"

The rule in action here is that matching a derived class is considered better than matching a base class when selecting a function from an overloaded set.

Finally, an interesting twist — the inheritance doesn’t necessarily have to be public. Access rules are orthogonal onto overloading rules. ( If private, #1 will be error by gcc: ConstantString' is an inaccessible base ofTemporaryString’, because conversion to the private base of TemporaryString leads to inaccessibility. This error can be eliminated by adding to class ConstantString a constructor taking a String as parameter , ie ConstantString(const String &){} )

——————————————————————————————————————————————————————————————————————

boost::is_base_and_derived

template <typename B, typename D>
struct bd_helper
{
    template <typename T>
    static type_traits::yes_type check_sig(D const volatile , T);
    static type_traits::no_type check_sig(B const volatile
, int);
};

template<typename B, typename D>
struct is_base_and_derived_impl2
{
    struct Host
    {
        operator B const volatile () const;
        operator D const volatile
();
    };
    static const bool value = sizeof(bd_helper<B,D>::check_sig(Host(), 0)) == sizeof(type_traits::yes_type);
};
// Host() 临时对象是一个右值

Let’s take the multiple base class below as an example, and the following
will also show why there’s not a problem with private or ambiguous base
class:

struct B {};
struct B1 : B {};
struct B2 : B {};
struct D : private B1, private B2 {};

is_base_and_derived<B, D>::value;

First, some terminology:

SC - Standard conversion
UDC - User-defined conversion

A user-defined conversion sequence consists of an SC, followed by an UDC,
followed by another SC. Either SC may be the identity conversion.

When passing the default-constructed Host object to the overloaded check_sig()
functions (initialization 8.5/14/4/3), we have several viable implicit
conversion sequences:

For "static no_type check_sig(B const volatile , int)" we have the conversion
sequences:

C -> C const (SC - Qualification Adjustment) -> B const volatile
(UDC)
C -> D const volatile (UDC) -> B1 const volatile / B2 const volatile ->
     B const volatile
(SC - Conversion)

For "static yes_type check_sig(D const volatile , T)" we have the conversion
sequence:

C -> D const volatile
(UDC)

According to 13.3.3.1/4, in context of user-defined conversion only the
standard conversion sequence is considered when selecting the best viable
function, so it only considers up to the user-defined conversion. For the
first function this means choosing between C -> C const and C -> C, and it
chooses the latter, because it’s a proper subset (13.3.3.2/3/2) of the
former. Therefore, we have:

【 C++ primer
类似地很可能有两个构造函数都能被用来把一个值变成转换的目标类型。在这种情况
下,在用户定义转换之前的标准转换序列会成为“选择最佳的用户定义转换序列”的依据
例如:
class SmallInt {
    public:
    SmallInt( int ival ) : value( ival ) { }
    SmallInt( double dval )
    : value( static_cast< int >( dval ) )
    { }
};
extern void manip( const SmallInt & );
int main() {
    double dobj;
    manip( dobj ); //ok: SmallInt( double )
}

【 C++ primer
如果两个转换函数都可以使用,则跟在转换函数之后的标准转换序列会成为选择最佳
的用户定义转换序列的依据。
class Number {
public:
    operator float();
    operator int();
};
Number num;
float ff = num;   // operator float()

【 C++ primer
class SmallInt {
public:
    operator int();
    operator float();
    // …
};
void compute( float );
void compute( char );
SmallInt si ( 68 );
main() {
    compute( si ); // 二义的
}
而这里考虑的是两个不同的函数,带有不同的参数类型,且目
标类型是变化的。如果两个参数类型要求不同的用户定义的转换,则不可能选择哪一个参数
类型比另一个更好,除非用户定义的转换涉及到相同的转换函数。

因为两个用户定义的转换序列使用了不同的转换函数,所以不能判定哪个函数对该
调用有最佳的参数类型。标准转换序列的等级也不能被用来判定最佳转换序列,以及最佳参
数类型,所以,该调用被编译器标记为二义的。

C -> D const volatile (UDC) -> B1 const volatile / B2 const volatile ->
     B const volatile
(SC - Conversion)
C -> D const volatile (UDC)

Here, the principle of the "shortest subsequence" applies again, and it
chooses C -> D const volatile
. This shows that it doesn’t even need to
consider the multiple paths to B, or accessibility, as that possibility is
eliminated before it could possibly cause ambiguity or access violation.

If D is not derived from B, it has to choose between C -> C const -> B const
volatile for the first function, and C -> D const volatile for the second
function, which are just as good (both requires a UDC, 13.3.3.2), had it not
been for the fact that "static no_type check_sig(B const volatile , int)" is
not templated, which makes C -> C const -> B const volatile
the best choice
(13.3.3/1/4), resulting in "no".

Also, if Host::operator B const volatile hadn’t been const, the two
conversion sequences for "static no_type check_sig(B const volatile
, int)", in
the case where D is derived from B, would have been ambiguous.



————————————————————————————————————————————————————————————


C++ primer

右值只是一个表达式,它表示了一个值或一个引用了临时对象的表达式,用户不能寻址该
对象,也不能改变它的值。

精确匹配的实参并不一定与参数的类型完全一致,有一些最小转换可以被应用到实参上。
在精确匹配的等级类别中可能存在的转换如下:
    从左值到右值的转换
    从数组到指针的转换
    从函数到指针的转换
    限定修饰转换(这种转换只影响指针,它将限定修饰符const或volatile 或两者加到指针指向的类型上)

即使一个实参必须应用一些最小的类型转换,才能将其转换为相应函
数参数的类型,它仍然是精确匹配的。



一个标准(standard) 转换序列潜在地由下列转换以下列顺序构成:
    左值转换——>
        提升或者标准转换——>
            限定修饰转换
(至多每种转换会有一个被应用上,以将实参转换成相应的参数)
这种转换序列被称为标准(standard) 转换序列,还有另外一种转换序列被称为用户定义
的user-defined 转换序列。

限定转换(把const 或volatile 修饰符加到指针指向的类型上的转换)具有精确匹配的等
级。但是,如果两个转换序列前面都相同,只是一个在序列尾部有一个额外的限定转换,则
另一个没有额外限定转换的序列比较好。eg:
void reset( int );
void reset( const int
);