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.
156 lines
4.4 KiB
156 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");
|
|
}
|
|
|