7.4 KiB
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.
std::map<std::string, float> collection;
decltype(collection) other; // other has type of collection
decltype(collection)::mapped_type value; // value is float
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) // from C++14 decltype not necessary
{
return a + b;
}
It is useful in various scenarios:
auto compare = [](const auto &first, const auto &second) {
if (first.size() == second.size()) {
return first < second;
}
return first.size() < second.size();
};
std::map<std::string, int, decltype(compare)> 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:
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<int> 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<std::string, int, Compare> 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.
template<typename Fun, class... Args>
decltype(auto) Example(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(args)...);
}
Let's test it
We have the following class:
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<std::string, std::string> 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)
template<typename Fun, class... Args>
auto RunFun(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(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)
template<typename Fun, class... Args>
decltype(auto) RunFun(Fun fun, Args&&... args)
{
return fun(std::forward<Args>(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
template <typename T>
constexpr T pi = T(3.141592653589793238462643383);
// Usual specialization rules apply:
template <>
constexpr const char* pi<const char*> = "pi";
template <>
constexpr const int pi<const int> = 4;
int main() {
std::cout << pi<double> << '\n'; // 3.14159
std::cout << pi<const char*> << '\n'; // pi
std::cout << pi<int> << '\n'; // 3
std::cout << pi<const int> << '\n'; // 4
return 0;
}
Binary literals
int main() {
std::cout << 0b10101010 << '\n'; // 170
const int val = 0b1111;
std::cout << (val ^ 0b1010) << '\n'; // 5
return 0;
}
Digit separators
const int milion = 1'000'000;
const double val = 123'456'789'101.000;