Thursday, December 15, 2016

C++ tips, 2016 Week 49 (5-Dec - 11-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. Capturing members by value

Imagine you want to return a lambda from an object that does some computations later but captures part of the state of that object:

class Boo { 
public: 
auto getValueMultiplier() { 
return [=](int multiplier) { 
return value * multiplier; 
}; 
} 
private: 
int value = 5; 
};



One can easily assume that value is captured by value but if we try to use it after the end of the lifetime of that object as we can see in the example the results are a little different.

What happens is that only the this pointer is captured by value and that data members are accessed via the captured this pointer - in fact it is this->value in the lambda. When the object is destroyed accessing value inside the lambda is like accessing random memory location.

To resolve this the Committee added capturing *this by value in C++17.

2. inline namespaces

Inline namespaces allow us to provide a form of versioning. It is basically a "default" nested namespace that is omitted when using the outer namespace.

namespace mylib { 
   inline namespace v1 { 
       void print() { 
           std::cout << "v1\n"; 
       } 
   } 
}  
int main() { 

   mylib::print(); // note - v1 is omitted 

} 


Later we upgraded our library with new version of the function but we want the users to still be able to use the old functionality:

namespace mylib 
{ 
   inline namespace v2 { //we moved inline here 
       void print() { 
           std::cout << "v2\n"; 
       } 
   } 
   namespace v1{ 
       void print() { 
           std::cout << "v1\n";
       } 
   } 
}
int main(){ 

   mylib::print(); // now we are using v2 without forcing the users of mylib to do anything 

   mylib::v1::print() //however they can still use the older version 

}


This example on wandbox.

3. if/switch with initializer

Coming in C++17 the syntax of if/switch statements with initializer is as follows 
if constexpr(optional) ( init-statement condition )
statement-true
else
statement-false
Is syntactically equivalent to
{
init_statement
if constexpr(optional) ( condition )
statement-true
else

statement-false
}
one benefit is that  the variables declared in the init-statement just like in the for loop are not leaked in the ambient scope:
auto it = m.find(10);
if (it != m.end()) {
return it->size();
} // "it" is leaked into the ambient scope.

if (auto it = m.find(10); it != m.end()) {
return it->size();
} // "it" is destructed and than undefined
More examples can be found here (check this self-promoting out). Same goes for the switch statement:

switch (Foo x = make_foo(); x.status())
{
case Foo::FINE: /* ... */
case Foo::GOOD: /* ... */
case Foo::NEAT: /* ... */
default: /* ... */
}

4. Generic lambdas

Ah! The beauty of using the standard algorithms prior to C++14:

for_each( begin(v), end(v), [](const decltype(*begin(v))& x) { cout << x; } );

sort( begin(w), end(w), [](const shared_ptr<some_type>& a, 
                          const shared_ptr<some_type>& b) { return *a<*b; } );

auto size = [](const unordered_map<wstring, vector<string>>& m) { return m.size(); };


Luckily C++14 introduced generic lambdas where this becomes:

for_each( begin(v), end(v), [](const auto& x) { cout << x; } );

sort( begin(w), end(w), [](const auto& a, const auto& b) { return *a<*b; } );

auto size = [](const auto& m) { return m.size(); };

Note that the last one works with all classes that have size() method - not only unordered_map<wstring, vector<string>>.

The underlying implementation (as we all know lambdas are syntactic sugar for functors structs with operator() (we no longer call them functors - we use callables instead to avoid the collision with functors from the Category Theory where it means a completely different thing)

The underlying implementation should be something like this. This lambda :

int multiplier = 2, sum = 0;
[multiplier, &sum](auto& item){ sum += item.Width() * multiplier; }


is replaced by something like this:

class _CompilerGeneratedNotReadable_
{
public:
_CompilerGeneratedNotReadable_(int& sum, int multiplyer) : sum_{s}, multiplier_{m} {}

template<class T>
void operator() (T& item) const
{
sum_ += item.Width() * multiplier_;
}
private:
int& sum_;
int multiplier_;
} 

In non-generic lambdas the operator() is not a template.

5. boost::flyweight 

boost::flyweight is the Boost implementation of the famous Flyweight design pattern. The idea of this pattern is to save memory by avoiding duplication when you have a huge collection of elements which have a lot of identical immutable parts.

The usage is pretty straightforward. Having this struct, for example:

struct contact_info { 
   std::string country; 
   std::string town; 
 ... 
};

 we convert it to:

#include <boost/flyweight.hpp> 
struct contact_info { 
   boost::flyweight<std::string> country; 
   boost::flyweight<std::string> town; 
 ... 
};

The flyweight object is implicitly convertible to const T&, for example:

std::string location; 
Location += info.country + " " + info.town;


and you also have a .get() method that returns a const T& to the underlying object

The thing that you should remember when switching to boost::flyweight is that it is designed to work with immutable data. You can not change the underlying objects but you can assign new value to the boost::flyweight object itself - it will rearrange its internals and will start pointing to the new immutable underlying object.

For deeper understanding of it you should read the documentation.

Bonus MSVC/Boost pro-tip that I've discovered yesterday - I'm big fan of semantic colorization but Boost uses additional file extensions - .ipp and .inl - that are not parsed as C++ by default. Apparently MSVC always had this capability to link arbitrary file extension to a language - Options/Text Editor/File Extension.

No comments:

Post a Comment