## Cpp14 * A quick reminder of lesser known features * decltype(auto) * Variable templates * Binary literals (Finaly!) * Digit separators ___ ## `decltype` **Rationale**: Deduction provided in contexts where auto is not allowed. `decltype` allows a compiler to deduce the type of the variable or expression, eg. the returned type can be deduced from function parameters. ```cpp std::map collection; decltype(collection) other; // other has type of collection decltype(collection)::mapped_type value; // value is float template auto add(T1 a, T2 b) -> decltype(a + b) // from C++14 decltype not necessary { return a + b; } ``` ___ It is useful in various scenarios: ```C++ auto compare = [](const auto &first, const auto &second) { if (first.size() == second.size()) { return first < second; } return first.size() < second.size(); }; std::map map(compare); map.emplace("C++20", 20); map.emplace("C++1234", 1234); map.emplace("Bababab", 12); map.emplace("Abababa", 13); for (const auto &[standard, number] : map) { std::cout << "Standard: " << standard << " | number: " << number << '\n'; /* Output: Standard: C++20 | number: 20 Standard: Abababa | number: 13 Standard: Bababab | number: 12 Standard: C++1234 | number: 1234 */ } ``` ___ Since C++20 we can use lambda expressions in unevaluated operands: ```C++ using SquareRoot = decltype([](const int val) { return std::sqrt(val); }); using Compare = decltype([](const auto &first, const auto &second) { if (first.size() == second.size()) { return first < second; } return first.size() < second.size(); }); int main() { std::vector vec(30); std::iota(begin(vec), end(vec), 0); std::transform(begin(vec), end(vec), begin(vec), SquareRoot{}); for (const auto& el : vec) { std::cout << el << ' '; } // Output: // 0 1 1 1 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 5 5 5 5 5 // Compare lambda will be constructed by default std::map map; map.emplace("C++20", 20); map.emplace("C++1234", 1234); map.emplace("Bababab", 12); map.emplace("Abababa", 13); } ``` Closure types are not default constructible before C++20. In C++20 a closure type that has no capture is default constructible. That's why we can do a litle magic here :) ___ ## `decltype(auto)` `decltype(auto)` deduction mechanism preserves type modifiers (references, const, volatile). `auto` deduction mechanism does not preserve type modifiers. When you write generic code you want to be able to perfectly forward a return type without knowing whether you are dealing with a reference or a value. ```cpp template decltype(auto) Example(Fun fun, Args&&... args) { return fun(std::forward(args)...); } ``` ___ ## Let's test it We have the following class: ```C++ class Server { public: bool addRequest(const std::string& serviceId, const std::string& request) { return requests_.emplace(serviceId, request).second; } std::string& getRequest(const std::string& serviceId) { if (const auto it = requests_.find(serviceId) ; it != std::cend(requests_)) { return it->second; } throw std::runtime_error("Invalid serviceId"); } private: std::map requests_; }; int main() { Server server; server.addRequest("SuperService", "Eat meat first!"); server.getRequest("SuperService") += " Leave the potatoes"; std::cout << server.getRequest("SuperService") << '\n'; // Eat meat first! Leave the potatoes } ``` ___ Now let's use it with generic function, but without `decltype(auto)` ```C++ template auto RunFun(Fun fun, Args&&... args) { return fun(std::forward(args)...); } int main() { Server server; server.addRequest("SuperService", "Eat meat first!"); // Write that we want to return std::string& RunFun([&server](const auto& id) -> std::string& { return server.getRequest(id); }, "SuperService") += " Leave the potatoes"; std::cout << server.getRequest("SuperService") << '\n'; // Output: Eat meat first! // Compiler didn't emit any warning! } ``` ___ Now fix this with `decltype(auto)` ```C++ template decltype(auto) RunFun(Fun fun, Args&&... args) { return fun(std::forward(args)...); } int main() { Server server; server.addRequest("SuperService", "Eat meat first!"); // Write that we want to return std::string& RunFun([&server](const auto& id) -> std::string& { return server.getRequest(id); }, "SuperService") += " Leave the potatoes"; std::cout << server.getRequest("SuperService") << '\n'; // Output: Eat meat first! Leave the potatoes } ``` Now we avoid misleading and don't waste time on debugging sessions! ___ ## Variable templates ```C++ template constexpr T pi = T(3.141592653589793238462643383); // Usual specialization rules apply: template <> constexpr const char* pi = "pi"; template <> constexpr const int pi = 4; int main() { std::cout << pi << '\n'; // 3.14159 std::cout << pi << '\n'; // pi std::cout << pi << '\n'; // 3 std::cout << pi << '\n'; // 4 return 0; } ``` ___ ## Binary literals ```C++ int main() { std::cout << 0b10101010 << '\n'; // 170 const int val = 0b1111; std::cout << (val ^ 0b1010) << '\n'; // 5 return 0; } ``` ## Digit separators ```C++ const int milion = 1'000'000; const double val = 123'456'789'101.000; ```