Valid tive Modern C ++ Item 27: overload the universal references, inclutivemodernc

Source: Internet
Author: User

Valid tive Modern C ++ Item 27: overload the universal references, inclutivemodernc

Assume that there is a template function foo that receives the universal references, which is defined as follows:

template<typename T>void foo(T&& t){    cout << "foo(T&& t)" << endl;}

If you want to perform special processing on some types, write an overloaded version of foo. For example, if you want to perform special processing on the float type, write a foo that receives the float type:

void foo(float n){    cout << "foo(float n)" << endl;}

In this way, if we write foo (1.0), we should theoretically output "foo (float n)", but the actual output is "foo (T & t )". Why? Because "Functions taking universal reference are the greediest functions in C ++", that is to say, the function of the universal reference can accurately match almost all types. When we call foo (1.0), 1.0 is derived as the double type. If we call foo (float n), we need to perform the narrow conversion, therefore, the compiler will regard foo (T & t) as a more accurate match, unless we write foo (1.f), it will call foo (float ). The overloaded version is called only when the type is completely accurate. Otherwise, the compiler always considers the universal reference version as accurate match.

This problem is more concealed in class inheritance. Suppose there is a class named Base, and Base has a template constructor that receives the universal reference, which is defined as follows:

class Base{public:    template<typename T>    explicit Base(T&& t)    {        cout << "Base(T&& t)" << endl;    }    Base(const Base& b)    {        cout << "Base(const Base& b)" << endl;    }    Base(Base&& b)    {        cout << "Base(Base&& b)" << endl;    }    Base() = default;};

Then Derived inherits Base:

class Derived : public Base{public:    Derived() = default;    Derived(const Derived& d)        :Base(d)    {    }    Derived(Derived&& d)        :Base(std::move(d))    {    }};

At this time, if we write:

Derived a;Derived b(a);Derived c(std::move(a));

The output result is always "Base (T & t)", that is, in the copy structure and mobile structure of Derived, Base function calls are Base (T & t ). Because the type passed to the Base is Derived, the compiler always considers the universal reference as an exact match.

Because the match of the universal reference is too "strong", it is generally necessary to avoid overloading. Otherwise, the matching results may be inconsistent with expectations.

What if overload cannot be avoided? There are two methods:

1. Use type tags

Considering the previous foo function, we do not reload the foo function. Instead, we compile two overloaded fooImpl, one of which accepts the universal reference and the other accepts the float. The two functions are distinguished by the type tag parameter:

template<typename T>void fooImpl(T&& t, std::false_type){    cout << "fooImpl(T&& t)" << endl;}void fooImpl(float t, std::true_type){    cout << "fooImpl(float t)" << endl;}

If the type tag of the parameter indicates whether it is a floating point type, foo can be called as follows:

template<typename T>void foo(T&& t){    fooImpl(std::forward<T>(t), std::is_floating_point<std::remove_reference_t<T>>());}

With the type tag, the float version fooImpl is called as long as the parameter is of the floating point type.

2. Use enable_if to constrain the universal reference

If in some cases we do not want to use the universal reference version, we can use enable_if to block it from the candidate functions that overload resolution (SFINAE mechanism ).

For the foo function, rewrite it:

template<typename T, typename = std::enable_if_t<    !std::is_floating_point<    std::remove_reference_t<T>    >::value>>void foo(T&& t){    cout << "foo(T&& t)" << endl;}void foo(float t){    cout << "foo(float t)" << endl;}

In this way, if the parameter is of the floating point type, foo (T & t) will be blocked and the foo of the float version will be called, which is consistent with the expected result.

The same idea applies to the previous Base class. Use enable_if to rewrite the previous Code:

class Base{public:    template<typename T, typename = std::enable_if_t<        !std::is_base_of <Base, std::decay_t<T>>::value        >>    explicit Base(T&& t)    {        cout << "Base(T&& t)" << endl;    }    Base(const Base& b)    {        cout << "Base(const Base& b)" << endl;    }    Base(Base&& b)    {        cout << "Base(Base&& b)" << endl;    }    Base() = default;};

In this way, the copy construction and moving construction of Derived can correctly call the Base function (std: decay remove references and cv-qualifiers ).

 

Conclusion:

1. avoid overloading the universal references template function whenever possible.

2. if it cannot be avoided, use type tags or enable_if to compile the overload function.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.