Oct 14, 2018

[C++] C++17 RVO is the standard.

With side-project on Envoy, found something interesting thus jot down for future reference.

  • Idiom:
    Functions take values as arguments and std::move to final destination. (Less constructor/destructor calls involved)

    Or use template trick to limit the universal constructor having higher priority then non-template ones.
  • C++17 has RVO into the language. Thus,
    -fno-elide-constructors
    will not turn off RVO in any how.
  • is_convertible, used as the guard for universal constructor to allow copy constructor takes itself type's instance or derived one.
    However, is_convertible isn't using the 'cast' as implementation, which is in run-time, it simply uses a form like:
    To test() { return std::declval<From>(); } // ODR use of declval here, which static_assert fails. It's just an example.
    That is to say, any type with conversion operator would meet the is_convertible requirement, not only the derived ones.
  • explicit is your friend. Single parameter constructor, including template ones, use explicit prevent automatic type conversion by accident.

As usual, one example explains what mentioned above.
#include <boost/type_index.hpp>
#include <iostream>
#include <string_view>
#include <utility>

using namespace std;


struct Base {
    // -- 1
    // Guarded , using enable_if_t, is_convertible_v for C++17
    template <typename T, typename = typename enable_if<
                              !is_convertible<T, Base>::value>::type>
    explicit Base(T &&t)
    {
    }

    Base(const Base &) {}  // on purpose, compiler generates by default -- 2

    Base(Base &&) {}  // on purpose, compiler generates by default. -- 3
};

struct Derived : Base {
    using Base::Base;
};

struct Mutation {
    explicit operator Base()
    {
        return Base{42}; // Hits (1)
    }
};

int main()
{
    auto _ = "Pikachu"sv;

    Mutation m;
    // static_cast<Base>(m);  // Can't call this way due to Base's universal
    // constructor installed.
    Base b1{
        m.operator Base()}; // -fno-elide-constructors has no effect for C++17
    // By default Move constructor will never be hit in C++17
    // Prior then C++17, Move constructor will be hit if no -fno-elide-constructors
    Base b2{D{42}}; // Hits (3), no RVO, RVO only happens on _same_ type.
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.