Thursday, December 8, 2016

C++ tips, 2016 Week 48 (28-Nov - 4-Dec-2016)

This is part of my weekly C++ posts based on the daily C++ tips I make at my work. I strongly recommend this practice. If you dont have it in your company start it. 
List of all weekly posts can be found here.

1. std::ratio

std::ratio is a class template for compile time rational arithmetics. Coupled with several alias templates for operations like std::ratio_add, std::ratio_multiply, etc you can do compile time arithmetics with rational numbers:

#include <iostream> 
#include <ratio> 
 
int main() 
{ 
   using one_fourth = std::ratio<2, 8>; 
   using two_third = std::ratio<2, 3>; 
 
   using sum = std::ratio_add<two_third, one_fourth>; 
   std::cout << "1/4 + 2/3 = " << sum::num << '/' << sum::den << '\n'; 
}

std::ratio is prerequisite for std::chrono particularly std::chrono::duration. Which is prerequisite for <thread> which is one of the fundaments of the concurrency along with <future> which requires perfect forwarding to work properly (otherwise we cant have std::async which also needs variadic templates to be cool and not use like 30-500 macroses). std::future also has to be movable but not copiable - needs move semantics. So is it now clear why it took so much time to ship C++11?

2. splice

std::list::splice is a neat function of std::list that is used to move nodes from one list to another without affecting the objects stored in those nodes.


#include <iostream> 

#include <list> 
 
int main () 
{ 
   std::list<int> list1 = { 1, 5, 6 }; 
   std::list<int> list2 = { 2, 3, 4, 99 }; 
 
   list1.splice(++list1.begin(), list2, list2.begin(), --list2.end()); 
 
   std::cout << "list1: "; 
   for (auto &i : list1) std::cout << " " << i; 
   std::cout << '\n' << "list2: "; 
   for (auto &i : list2) std::cout << " " << i; 
}

No moving or copying is done during splice since it only rearranges the pointers of the nodes in both lists. Also note that  behaviour is undefined if get_allocator() != other.get_allocator() (if elements are of the same type but use different allocators).

The good news is that from C++17 other STL containers (maps, sets, unordered_maps, etc) also receive similar functionality for extracting nodes from one container and merging them into another of the the same type. 

Note. Since the operations are similar to erasing and inserting elements if the functionality of the container is affected some more steps may have to be done (rebalancing for example).

3. boost::join

boost::algorithm::join joins strings from a container into a new string separated by a user provided separator. Also includes a version with a predicate that can filter the elements.


#include <boost/algorithm/string/join.hpp> 
#include <vector> 
#include <iostream> 

int main(int, char **) 
{ 
   std::vector<std::string> words{"one", "two", "three", "four"}; 
   std::string joined = boost::algorithm::join(words, ", "); 
   std::cout << joined << std::endl; 
}
Its very handy when you need it. 

4. Using IIFE for const variable initialization

IIFE stands for Immediately-invoked function expression basically a lambda that is executed immediately after its definition. For example:
int x = []() { return 5*6;}(); //note the () at the end. That invokes the lambda
In some cases we end up with something like this:
void foo(int parameter1, int parameter2) 

{ 
   int factor; 
   if (parameter1) 
  factor = computeFactor(parameter2); 
   else 
       factor = parameter2 + 20; 
......................... 
}


where factor is never changed afterwards and by all means it should be const. IIFE to the rescue:

void foo(int parameter1, int parameter2) 
{ 
   const auto factor = [&]{ 
       if (parameter1) 
       return computeFactor(parameter2); 
       else 
           return parameter2 + 20; 
       }(); 
......................... 
}

As we can see the compilers are fully capable to optimize it - IIFE example on Compiler Explorer.

P.S. I stole this from was influenced by Bartek's  blog post about the very same issue.

5. Reference qualifiers for member function

Coming from the debts of the Standard this feature allows you to call one member function overload when the object is lvalue and another when the object is rvalue. Or more specifically you can specify the object behavior depending on whether it is a temporary object or a named one. The syntax is as follow:

class Foo 

{ 
   void doTheJob() &; 
   void doTheJob() &&; 
}

It usually does not makes sense to make const functions && because they are supposed to benefit from the knowledge that the temporary object will not exists soon and modify it/extract stuff form it before it is gone

A practical example is overloading operator+ to not create a temp variable when called from rvalue object:

class Foo: 

{ 
   // additional stuff like operator+=, move/copy operators, etc
   ................... 
 
   Foo operator+(const Foo& foo) & 
   { 
       Foo tmp(*this); 
       tmp += foo; 
       return tmp; 
   } 
     
   Foo operator+(const Foo& foo) && { 
       *this += *foo; 
       return std::move(*this); 
   } 
} 

 
Foo a1, a2; 
Foo b1, b2; 
........ 
b1 = a1 + a2; // calls operator+(const Foo& foo) & - one additional variable is involved 
b2 = Foo() + a1; // calls Foo operator+(const Foo& foo) && - we are adding a1 to the temp object ( Foo() ) and moving the temp object into b2

You can find the complete example here


No comments:

Post a Comment