Thursday, January 19, 2017

C++ tips, 2017 Week 2 (9-Jan - 15-Jan-2017)

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::forward

std::forward helps ... forward a T&& function argument as-is to another function. In the following example:
template<class T>
void choose_foo(T&& arg)
{
   foo(arg);
}
arg is treated as lvalue and will call the T& overload of foo. However if we pass a temporary to choose_foo we probably want to call the T&& overload of foo (if any). To fix this we use std::forward:
template<class T>
void choose_foo(T&& u)
{
   foo(std::forward<T>(u));
}

It also preserves constness so if passed with const T& argument it will call the appropriate overload of foo -  Wandbox example.

The variadic template usage (it is kind of idiom) is like this:
template <typename ...Params>
void moo(Params&&... params)
{
   boo(std::forward<Params>(params)...);
}
 2. Two phase lookup

In a template we have two categories of names - dependent and non-dependent on the template parameters.

Non-dependent names are resolved at template definition and dependent ones are resolved at template instantiation thus two-phase lookup. For example:
int foo(void*) {
   return 1;
}


template<typename T>
int foo_caller() {
   return foo(0);
}


int foo(int) {
   return 0;
}


int main() {
   std::cout << foo_caller<int>() << '\n';
}
should output 1 because foo is non-dependent on the template parameters and is resolved at the definition of foo_caller. If you compile it with MSVC it will output 0 because of MS compiler implementation limitations. However people at MS are fixing this.

3. Boost.Circular Buffer 

Boost.Circular Buffer is implementation of the circular (or ring) buffer container. It has preset maximum amount of elements and when that limit is reached it overwrites the oldest element. Overwrites as in using the assignment operator - destructing the old element and constructing the new one into its place.

It is as with other Boost containers it is designed to work with the STL algorithms. And as with STL containers there are no thread safety guarantees or notifications that you try to read from an empty container - that is a performance overhead that no one should be forced to get.You have to preallocate the whole memory needed before usage but there is also an adaptation optimizing on space - it allocates memory only when needed but as you may guess it is a little bit slower.

For better performance it is designed to use continuous memory block which combined with the functionality creates some interesting side effects. 
boost::circular_buffer<int> cb(3);

cb.push_back(1);
cb.push_back(2);
cb.push_back(3);

boost::circular_buffer<int>::iterator it = cb.begin();

assert(*it == 1);

cb.push_back(4);

assert(*it == 4); // The iterator still points to the initialized memory.
Pushing back 4 overwrites the oldest element - in this case 1 - the iterator still points to initialized memory but it is being changed. Interesting implementation detail. Do don't rely on this in production code.

4. Compile time tetris

Never give up your dreams! Nothing is impossible! You can achieve anything! Never underestimate the capabilities of a driven developer - in this case Matt Bierner who wrote compile time template tetris. Yes - C++17 tetris played at compile time.
I find it fascinating - you compile each time with a different flag (-D LEFT  for example) and get the next state of the game, than you run it to view the state and than repeat. Awesome.

5. No 5. Again. I'm really getting behind schedule. Sorry!

No comments:

Post a Comment