A repository where I'll put experiments learning, proving or disproving things about C++. These experiments will feature measurements, use Tracy to show behavior, and generally have the goal of simplifying normal C++ usage.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
c-plus-plus-dafuq/rvo/test.cpp

157 lines
4.4 KiB

#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <string>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace fmt;
// Some heavy object
struct A {
A() = default;
A(int val) : val(val) {
std::cout << "called constructor with: " << val << std::endl;
}
A(A const& other) {
std::cout << "calling copy(A const& other): " << val << std::endl;;
}
A(A&& other) {
std::cout << "calling move(A&& other): " << val << std::endl;;
}
A& operator=(A&& other) {
std::cout << "calling move(A&& other)=: " << val << std::endl;;
return *this;
}
A& operator=(A const& other) {
std::cout << "calling copy(A const& other)=: " << val << std::endl;;
return *this;
}
~A() = default;
int val{};
};
template <typename T>
void print_helper(std::string_view var, T&&) {
std::string name = typeid(T).name();
if constexpr (std::is_const_v<T>) {
name += " const";
}
if constexpr (std::is_lvalue_reference_v<T>) {
name += "&";
} else {
name += "&&";
}
std::cout << "Type of '" << var << "': '" << name <<"'\n";
}
#define print_t(VAR) print_helper(#VAR, VAR)
A rvo_impl_1() {
A a;
std::cout << "rvo_impl_1\n";
a.val = rand() % 2;
return a;
}
// calls the move constructor or it might optimize away it in -O3
std::string rvo_impl_2(int v) {
std::string res;
std::cout << "rvo_impl_2\n";
res = std::to_string(v);
return res;
}
// It'll call copy constructors
std::string rvo_not_work_complex(int v) {
if (v % 5 == 0) return "multiple of five";
if (v % 2 == 0) return "multiple of 2";
return std::to_string(v);
}
// call move constructor
A rvo_not_work_impl(int v) {
std::cout << "rvo_not_work_impl: ";
A a;
if (v % 2) {
a.val = v % 2;
} else {
// returning early
// you might think who would do that let me show you an example
return {};
}
return a;
}
// copy-elision/RVO does not call any constructor since they get optimized away
A copy_elision() {
std::cout << "copy_elision\n";
return A(); // pr-value
}
// RVO(return value optimization) is a part of copy-elision, but "copy-elision" is
// a much wider concept that applies to other parts of the program. It removes
// copy/move constuctors
// What is l-value?
// l-values are references to the objects. These are objects you can hold onto
// like constructed objects, variables or references.
// example:
// 1. int&
// 2. int const&
// 3. int a = 4; foo(a <-- this will be a l-value)
// What is r-value?
// r-values are the values that is temporary.
// example:
// 1. literals like "hello", 1, 2.5
// 2. call to object constructor like A(), std::string("Hello"), ...
// 3. A&&, int&&, ... these are r-values that you've seen inside the move constructors
// There are more value that you can look into "pr-value", ...
// https://en.cppreference.com/w/cpp/language/value_category
// You may be thinking why i'm talking about them. The reason being these concept
// allows you understand when to use "std::move" and when it's useless. For that
// let me implement my version of move constructor
template <typename T>
// decltype(auto) means forward the return type or whatever I'm returning infer the type with
// "const&", "&" or "&&"
decltype(auto) my_move(T&& val) {
// here T&& is not a r-value. It's perfect forwarding. Whenever you see "&&" you see
// with template it means perfect forwading. I know cpp has fucked-up syntax.
using type = std::decay_t<T>;
// all this does is cast l-value to r-value
return static_cast<type&&>(val);
}
TEST_CASE("RVO tests", "[base]") {
A a{10};
int argc;
std::cout << "\n\n######### RVO TESTS ###############\n";
auto t0 = rvo_impl_1(); // no constructor will be called because of return value optimization
auto t1 = rvo_not_work_impl(argc);
auto t2 = copy_elision();
auto t3 = rvo_not_work_complex(argc);
auto t4 = rvo_impl_2(argc);
print_t(a); // pass by l-value | output: Type of 'a': 'A&'
print_t(std::move(a)); // pass by r-value | output: Type of 'std::move(a)': 'A&&'
print_t(my_move(a)); // pass by r-value | output: Type of 'my_move(a)': 'A&&'
print_t(A{}); // pass by r-value | output: Type of 'A{}': 'A&&'
static_assert(!std::is_rvalue_reference_v<decltype(a)>, "'a' is l-value");
// you can remove the negation and see it failing
static_assert(!std::is_lvalue_reference_v<decltype(my_move(a))>, "This should pass if I've implement it correctly");
}