UP | HOME

Variadic template and tuples

Table of Contents

1. Introduction

This is a small introduction to policy based design using c++. The way to think of policy-based design is that it provides a mean to enable a policy provided from the outside. Some abbriviations first:

Host
means the host template-class which policies are applied to.
Policy
a class with some functions providing a policy.

So what is the benefit of using policy based design? Why not just use inheritance? First of all it adds additional behavior to an existing code, without modifying the code itself. The consequence of this is that it becomes more modularized. The reason why using policies instead, is that in case the policy is nothing, then there will be no overhead. With virtual functions you always have quite amount of overhead. The policies are added at compile time and not at runtime.

Lets dive into a simple example.

2. Logger Example

The idea is to have logging for a class as a policy.

lets first construct a stupid logging class using fmt.

 1: class PissLog
 2: {
 3: public:
 4:   PissLog() = default;
 5: 
 6:   template<class Args>
 7:   void debug(std::string fmt, Args&& args) const
 8:   {
 9:     std::string newFmt = fmt::format("{:<20} {}\n", "Debug", fmt);
10:     fmt::print(newFmt, std::forward<Args>(args));
11:   }
12: 
13:   template<class Args>
14:   void error(std::string fmt, Args&& args) const
15:   {
16:     std::string newFmt = fmt::format("{:<20} {}\n", "Error", fmt);
17:     fmt::print(stderr, newFmt, std::forward<Args>(args));
18:   }
19: };

Then lets create another , which is just a dummy.

 1: class NullLog
 2: {
 3: public:
 4:   NullLog() = default;
 5: 
 6:   template<class Args>
 7:   void debug(std::string, Args&&) const
 8:   {}
 9: 
10:   template<class Args>
11:   void error(std::string, Args&&) const
12:   {}
13: };

The basic idea is that the Log classes just extends the class in which its used in by using private inheritance, its similair to UML-composition. Private inheritance don't make the class into something else, it just extends the class with new functionality of the inheritance. Lets first take a look at the class that uses the above logger.

 1: template<class Log>
 2: class Complicated : private Log
 3: {
 4: public:
 5:   using Log::debug;
 6:   using Log::error;
 7: 
 8:   Complicated() = default;
 9: 
10:   double divide(int x, int y)
11:   {
12:     debug("y={}", y);
13:     if (y == 0) {
14:       error("y=={}", y);
15:       return 0;
16:     } else {
17:       return x / y;
18:     }
19:   }
20: };

In this class we can see that we are using debug function and error function as described above. So now we could create a instance of class complicated and extend it with the functionality of one of our Log classes.

1: int main(int argc, char *argv[])
2: {
3:   Complicated< PissLog> cpl;
4:   cpl.divide(1,2);
5:   cpl.divide(1,0);
6:   return 0;
7: }

And if we compile and build we would get something like.

> ./main
> Debug                y=2
> Debug                y=0
> Error                y==0

Of course this was just an simple example. Lets for a minute imagine that we want another log class which writes to a file. That means we need at least two log classes. In our solution, this want work, so we need to figure out something else.

2.1. Composed Logger

Lets create a third logger, we call it ComposedLogger which can take arbitrary number of loggers, and for each debug call we will call each of the log instances with debug. Then we can use the composed Logger, with our complicated class.

By the above requirement we kind of recongnise that we need variadic templates. But not get ahead of ourselfs. Lets first checkout our Log classes that we are using.

 1: ///////////////////////////////////////////////////////////////////////////////
 2: //                                  PissLog                                  //
 3: ///////////////////////////////////////////////////////////////////////////////
 4: class PissLog
 5: {
 6: public:
 7:   PissLog() = default;
 8: 
 9:   template<class Args>
10:   void debug(std::string const& fmt, Args&& args) const
11:   {
12:     std::string newFmt = fmt::format("{:<20} {}\n", logName_, fmt);
13:     fmt::print(newFmt, std::forward<Args>(args));
14:   }
15: 
16:   template<class Args>
17:   void error(std::string const& fmt, Args&& args) const
18:   {
19:     std::string newFmt = fmt::format("{:<20} ERROR: {}\n", logName_, fmt);
20:     fmt::print(stderr, newFmt, std::forward<Args>(args));
21:   }
22: 
23:   std::string_view logName_{"PissLog"};
24: };
25: 
26: ///////////////////////////////////////////////////////////////////////////////
27: //                                   FmtLog                                  //
28: ///////////////////////////////////////////////////////////////////////////////
29: class FmtLog
30: {
31: public:
32: 
33:   template<class Args>
34:   void debug(const std::string& fmt, Args&& args) const
35:   {
36:     auto newFmt=fmt::format("{:<20} {}\n",logName_,fmt);
37:     fmt::print(newFmt,std::forward<Args>(args));
38:   }
39: 
40:   template<class Args>
41:   void error(std::string const& fmt, Args&& args) const
42:   {
43:     auto newFmt=fmt::format("{:<20} ERROR: {}\n",logName_,fmt);
44:     fmt::print(stderr,newFmt,std::forward<Args>(args));
45:   }
46: 
47:   std::string_view logName_{"FmtLog"};
48: };
49: 

As we can see there is no big difference from before, but instead of using NullLog we are using FmtLog, which is basically the same as PissLog, just for clarity.

Lets continue with the ComposedLogger we know it will use arbitrary number of loggers, and if we get a call to either debug or error method, then for each of the loggers-types that were provided (and instantiated) through the variadic template we need to call either debug or error, depending on the call.

 1: template<class... Logs>
 2: class ComposedLogger
 3: {
 4: public:
 5:   ComposedLogger()
 6:     : loggers_( Logs()... )
 7:   {}
 8: 
 9:   template<class Args>
10:   void debug(std::string const& fmt, Args&& arg)
11:   {
12:     log_debug(loggers_,
13:               fmt,
14:               std::forward<Args>(arg),
15:               std::index_sequence_for<Logs...>{});
16:   }
17: 
18:   template<class Args>
19:   void error(std::string const& fmt, Args&& arg)
20:   {
21:     log_error(loggers_,
22:               fmt,
23:               std::forward<Args>(arg),
24:               std::index_sequence_for<Logs...>{});
25:   }
26: 
27: private:
28:   std::tuple<Logs...> loggers_;
29: };
30: 
Line 1
The variadic template of Loggers. (e.g <PissLog,FmtLog> )
Line 6

This will instantiate each of the variadic templates types with the default constructor. std::tuple (1) has a direct constructor which initializes each element with corresponding parameters. Here is possible compiler rewrite using <FmtLog,PissLog> as parameters

1: ComposedLogger():
2:     loggers_{std::tuple<FmtLog,PissLog>(FmtLog(),Bepa())}
3: {}

The variadic template arguments are expanded and default initialized, and finally initialized into the loggers_ attribute.

Line 28
The tuple of Loggers, where each of the logger where instansiated on line 6.
Line 10
The debug method, this will call an additional function log_debug which we cover in the next section.
Line 15
Creates a index sequence which creates a compile-time sequence of indices. For our case we want to use the size of the tuple (or the number of items in our variadic template Logs), in other words create a index sequence from 0-sizeof(loggers). index_sequence_for is just an alias for template<class... T> using index_sequence_for = std::make_index_sequence<sizeof...(T)> Where T In our case is Logs. We need to provide a compile time sequence since its not possible to use runtime values when dealing with tuples. That is std::get<i>(tuple) in a for loop is not possible.
Line 19
And finally, basically the same as debug, though its calling error function instead.

Hopefully this was clear enough and that we straightend out the question marks regarding variadic templates. Lets now take a log at the Log_debug/error functions. These of course could be made as class method, but i choose to add them as stand-alone functions.

2.2. log_debug/error function c++11/14 way

Lets dive right into it. This is what the debug function looks like. Actually this only works in c++14, since std::index_sequence is not valid in c++11. To have the same functionality in c++11 we need to use recursion (I think….).

1: template<class Tuple,class Args, std::size_t... Ls>
2: void log_debug(const Tuple& tpls,std::string const& fmt,Args&& arg, std::index_sequence<Ls...>)
3: {
4:    (void)(std::initializer_list<int>{
5:     ((void)std::get<Ls>(tpls).debug(fmt,std::forward<Args>(arg)),0)...
6:       });
7: }
Line 1
The template takes 3 parameters Tuple which is the tuple that consists of all the logs. Args which is the argument to the format function. And finally the variadic template argument Ls, which is of type std::size_t... where ... means it is a sequence of indices, e.g 0,1,2,3,4,5... , but even if we have a sequence we need to somehow use each of the sequence number to call std::get<Ls[N]>(tuple) to retrieve the tuple we want to use. (take a look at Variadic sequence section for some explanation),
Line 5
In this case we use std::initializer_list. The initializer_list (IL) is constructs a array type object of some type (int in this case). Since we're not actually intrested in the array type we discard the return value with (void). And since debug is not actually returning anything we need to provide a default return value (0). The good thing is that we use the std::index_sequence that we created to force a for_each behaviour.

For more information on this can be read here.

2.3. log_debug/error function c++14/17 way

The std::initializer_list worked, but its kind of crude and hard to get right. Fortunatly c++17 came with another nice feature. The Fold expression, (see Variadic sequence on how it works), this feature made the std::initializer_list obsolete (for our circustance). So instead the function would look something like.

1: template<class Tuple,class Args, std::size_t... Ls>
2: void log_debug(const Tuple& tpls,std::string const& fmt,Args&& arg, std::index_sequence<Ls...>)
3: {
4:   (std::get<Ls>(tpls).debug(fmt,std::forward<Args>(arg)),...);
5: }

We still have the function definition with the template parameters, but we have removed the std::initializer_list with a fold expression. Much nicer and cleaner. The fold expression is only valid in c++17.

2.4. log_debug/error function c++17 way

To stretch it even further, there is a cleaner way of doing this. This part we actually changed the definition of the function. Instead of using fold expression we can use std::apply, well actually we do use fold expression, but in another way. Since the index_sequence is no longer needed, we can grab the opportunity that the variadic template (size_t…) has been removed, and use it to define the types in our tuple instead. This way it gets even more safe and easier to understand. Another cool feature which is using parameter packs in auto for lambda. What this means is that we can use auto... to get arbitrary number of parameters. E.g

1: auto f = [](auto... s)
2:          {
3:            (fmt::print("{} ",s),...);
4:          };
5: f("Hello", "world", "Another day","in paradise",12,2.3,'\n');

Here we use the fold expression almost like a for_each with the arguments (s).

> main
Hello world Another day in paradise 12 2.3

This seems like a useful construct, but we need to use each tuple item as an argument to the function to be able to use it in our purpose. So it doesn't solve the problem completely, we need some other feature to send the tuple as arguments to the lambda function. Fortunatly c++17 provides us with just such a feature which is called std::apply. The idea behind std::apply is to invoke a callable object with a tuple of arguments. E.g

1: auto f = [](auto... s)
2:          {
3:            (fmt::print("{} ",s),...);
4:          };
5: auto tpl = std::make_tuple("Hello", "world", "Another day","in paradise",12,2.3,'\n');
6: std::apply(f,tpl);

This means we can use the tuple that we created as argument to our lambda. Neat! And really useful since now we can rewrite our log_debug in a really nice way. I devoted a small section for a more thorough example working with std::apply, lambdas and parameter packs.

 1: template<class Args,class... T>
 2: void log_debug( std::tuple<T...> tuple, std::string const& fmt, Args&& args)
 3: {
 4:   std::apply([fmt,args](auto const&... item)
 5:              {
 6: 
 7:                (item.debug(fmt,std::forward<Args>(args)),... );
 8:              },
 9:     tuple);
10: }

Since the definiton of the function is changed, we also need to change the call to the function, we don't need the index_sequence any more and the call would be much easier to understand.

 1: template<class... Logs>
 2: class ComposedLogger
 3: {
 4: public:
 5:   ComposedLogger()
 6:     : loggers_( Logs()... )
 7:   {}
 8: 
 9:   template<class Args>
10:   void debug(std::string const& fmt , Args&& arg)
11:   {
12:     log_debug(loggers_, fmt, std::forward<Args>(arg));
13:   }
14: 
15:   template<class Args>
16:   void error(std::string const& fmt , Args&& arg)
17:   {
18:     log_error(loggers_, fmt, std::forward<Args>(arg));
19:   }
20: private:
21:   std::tuple<Logs...> loggers_;
22: };

2.5. Lambda auto and parameter pack

As described earlier this construct was really useful. But lets dive into some more elaborate constructs with this.

Here is a small snippet of a tuple-to-string

 1: template<class... T>
 2: std::string tuple_to_string(std::tuple<T...> const& tpl)
 3: {
 4: 
 5:   auto toString = [](auto val) -> std::string {
 6:     std::string strVal{ "Val:" };
 7:     if constexpr (std::is_arithmetic_v<decltype(val)>) {
 8:       return std::to_string(val) + "*";
 9:     } else {
10:       return std::string(val) + "#";
11:     }
12:   };
13: 
14:   std::string str;
15:   std::apply([&str, toString](auto... args)
16:              {
17:                str = (toString(args) + ...);
18:              }, tpl);
19: 
20:   return str;
21: }

Lets break it down.

Line 2
the function definition, recieves a tuple of types T... in other words its a tuple with arbitrary number of fields and types.
Line 5
A lambda function which converts to string. The argument is an auto which basically means it can be of any number of types, just like template<class T>. The function returns a string (->std::string).
Line 7
The if constexpr(expr) is a compile time construct, that means it will be evaluated during compile time. That also means that each of the types in the tuple needs to be known at compile time (which it is). The expression is evaluated using type traits and checks if its a arithmetic variable, that means its either floating type or integer type, in that case we need to convert it to a string (std::to_string). Any other sort is consider to be convertible to string via the std::string constructor.
Line 15
Invocation of std::apply, here we invoke a lambda by capturing

2.6. Variadic sequence.

First of all lets take a look at a function which doesn't use sequence.

1: 
2: template<std::size_t t>
3: void f()
4: {
5:   fmt::print("t={}\n", t);
6: }

Not much to it, the std::size_t is provided as a compile-time constant to the function, and is then used with fmt::print. But what if I want to do the same thing, but with a sequence of numbers? We know that variadic templates can be used to have an arbitrary number of arguments, in fact it is also possible to send in an arbitrary number of a fundamental type. E.g

1: template<std::size_t... ts>
2: void fs()
3: {...}

Now to be able to use those values we need some way of doing for_each of ts. Thats where fold expressions comes in, these are c++17 constructs, and can be used with operator (+,-,&&,||,…..), they can be right folded or left folded.

Lets take an example of right folded.

 1: template<std::size_t... ts>
 2: void fs()
 3: {
 4:   auto p = (ts - ...);
 5: }
 6: 
 7: int main()
 8: {
 9:   fs<10,3,2>
10:   return 0;
11: }

This extracted to something like:

1: unsigned long p = 10UL - (3UL - (2UL - 1UL));
2: 10 - (3 - (1))
3: 7 + 1
4: 8

If we now switch this to left folded

 1: 
 2: template<std::size_t... ts>
 3: void fs()
 4: {
 5:   auto p = (ts - ...);
 6: }
 7: 
 8: int main()
 9: {
10:   fs<10,3,2>
11:   return 0;
12: }

Now the output would be something like

1: unsigned long p = ((10UL - 3UL) - 2UL) - 1UL;
2: (7 - 2 ) -1
3: 5 - 1
4: 4

The difference between Right and Left has a major impact when considering using some operators (e.g -). One possible solution is to use "," operator together with a function.

E.g

 1: void fun( std::size_t i)
 2: {
 3:   std::cout << i << '\n';
 4: }
 5: 
 6: template<std::size_t... ts>
 7: void fs()
 8: {
 9:   (...,fun(ts) );
10: }
11: 
12: int main()
13: {
14:  fs<10,3,2,1>();
15: }

The call would be as exaplained before

1: ./main
2: 10
3: 3
4: 2
5: 1
6: 
7: 
8: //Right folded ((fun(10UL) , fun(3UL)) , fun(2UL)) , fun(1UL) 
9: //Left folded  fun(10UL) , (fun(3UL) , (fun(2UL) , fun(1UL)));

In this case the Left folded and Right folded should end up with the same result.

2.7. User-defined deduction

3. TODO Simple Expression Engine (SEE)

Another idea that I was thinking of is to use some kind of expression engine Lets for example pretend you want to use a check to see if class that is a combination of many other classes.

 1: class Pos {
 2: public:
 3:   int x_;
 4:   int y_;
 5: };
 6: 
 7: struct Camera {
 8: 
 9:   bool verify() const { return power_; }
10:   Pos pos_;
11:   bool power_;
12: };
13: 
14: struct Sensor {
15:   bool verify() const { return power_; }
16:   Pos pos_;
17:   bool power_;
18: };
19: 
20: class Security {
21: public:
22:   void turnOn() {
23:     for (auto &camera : cameras_) {
24:       camera.power_ = true;
25:     }
26: 
27:     for (auto &sensor : sensors_) {
28:       sensor.power_ = true;
29:     }
30:   }
31: 
32:   std::vector<Camera> cameras_;
33:   std::vector<Sensor> sensors_;
34: };

Now this is a real simple example, just for illustrative purposes. Lets imagine that we have a security system for a house.

Lets first construct the security system with 3 cameras and 3 sensors.

 1: Security security{
 2:     {
 3:       Camera{Pos{1,2},true},
 4:       Camera{Pos{2,3},true},
 5:       Camera{Pos{3,4},true},
 6:     },
 7:     {
 8:       Sensor{Pos{1,2},true},
 9:       Sensor{Pos{2,3},true},
10:       Sensor{Pos{3,4},true}
11:     }
12:     //Camera{{1,2},true}
13:   };

And as you leave the house you need to turn it on. Easily done with security.turnOn() . At this point , everything should be turned on, but we need to verify it some how. So lets construct how a lambda function that verifies a camera and a sensor, and put that in the namespace expressions.

 1: namespace expressions {
 2: 
 3: auto check_camera_power = [](auto security) {
 4:   auto pos =
 5:       std::find_if(std::begin(security.cameras_), std::end(security.cameras_),
 6:                    [](auto const &camera) { return !camera.verify(); });
 7:   if (pos != std::end(security.cameras_)) {
 8:     return false;
 9:   }
10: 
11:   return true;
12: };
13: 
14: auto check_sensor_power = [](auto security) {
15:   auto pos =
16:       std::find_if(std::begin(security.sensors_), std::end(security.sensors_),
17:                    [](auto const &sensor) { return !sensor.verify(); });
18:   if (pos != std::end(security.sensors_)) {
19:     return false;
20:   }
21: 
22:   return true;
23: };
24: 
25: } // namespace expressions

When all the cameras and sensors are verified , we can turn on the red-light (The system is engaged). If anything goes wrong we should notify the user. First of all, there might be times where we just want to check the sensors or maybe just the cameras, so somehow we need either combine, or use these functions as they are. Wouldn't it be nice if we could just create new functions based on the the functions that we have?

e.g

1: 
2: auto check_camera_and_sensors = expressions::check_sensors && expressions::check_cameras;
3: 
4: if( check_camera_and_sensors(security) )
5: {
6:   ....
7: }else
8: {....}

Well, that can be done. So lets do that.

 1: namespace expressions {
 2: 
 3: template<class Expr1,class Expr2>
 4: auto operator||(Expr1&& ex1,Expr2&& ex2)
 5: {
 6: 
 7:   return [ex1=std::move(ex1),ex2=std::move(ex2)](auto&& val)
 8:   {
 9:     return (ex1(std::forward<decltype(val)>(val)) || ex2(std::forward<decltype(val)>(val)));
10:   };
11: }
12: 
13: template<class Expr1,class Expr2>
14: auto operator&&(Expr1&& ex1,Expr2&& ex2)
15: {
16:   return [ex1=std::move(ex1),ex2=std::move(ex2)](auto val)
17:   {
18: 
19:     return (ex1(std::forward<decltype(val)>(val)) && ex2(std::forward<decltype(val)>(val)));
20:   };
21: }
22: }  // expressions

As long as where in the expression namespace we just declare a && operator and a || operator which constructs a new function, and is using the lhs and rhs as lambda capture. The keyword here is the new function, the operators (|| and &&), will infact return a new function which is the combination of the two functions provided.

.eg

1: auto check_camera_and_sensors = expressions::check_sensors && expressions::check_cameras;

The return (check_camera_and_sensors) is another lambda , and when that function is called the check_sensors and check_cameras are called (&& lambda captures) and the output is ANDed toghether to finally returns a bool. By using perfect forwarding we can use std::move to capture the function argument, this due to that the lambda will create two constructors, one for rvalues and one "by value", which will move it safely. So if we use:

1: auto f = [](int val){ return val >10; } && [](int val){ val < 20; }; //use rvalue, with std::move
2: auto f = Lt_10 && Gt_20; //Uses by value and std::move

Lets go further with this, we just installed a new sensor which just the mother of all sensors, so either that is on or cameras and sensors needs to be turned on. This sensors is placed on position Pos{0,0} . So lets construct a function that checks for the mother function, and then OR it with check_camera_and_sensors.

 1: auto mother_sensor = [](auto security)
 2: {
 3:   auto pos = std::find_if(std::begin(security.sensors_), std::end(security.sensors_),
 4:                           [](auto const& sensor) {
 5: 
 6:                             return (sensor.pos_ == Pos{0,0} && sensor.verify());
 7:                           });
 8: 
 9:   if (pos != std::end(security.sensors_)) {
10:     return true;
11:   }
12: 
13:   return false;
14: };

This is nice, though we forgot to add the ==-operator to Pos. So lets do that using lexicolgraphical comparison (std::tie).

1: bool operator==(Pos const& other) const
2: {
3:   return (std::tie(x_,y_) == std::tie(other.x_,other.y_));
4: }

Next we need the construct a new function to check our requirement. The same way as we did before , but we can actually use the function we created before.

.eg

1: auto all_system_on_or_mother_sensor = all_system_on || expressions::mother_sensor;

Which should be the same as:

1: auto all_system_on_or_mother_sensor = (expressions::get_check_sensor && expressions::check_camera_power)
2:                                       || expressions::mother_sensor;

We also need to add the mother sensor to our security system, and finally call the all_system_on_or_mother_sensor

security.sensors_.emplace_back(Sensor{Pos{0,0},true,"Mother Sensor"});
if( all_system_on_or_mother_sensor(security) )
{
  console->info("All system is on or mother sensor");
}else
{
  console->error("System is not available");
}

3.1. Extending it further

This feature can actually be extended a bit further. The problem with the above system, is that we cannot see which sensors/cameras that are failing. We could start iterating again, but that is a waste of time and performance. Instead we could use a capture to gain the list of failing sensors and cameras. But then we need somehow to send in a capture list of some sort. But then agin, by creating a new function using the argument that we can use as a capture and returning a lambda function should work. Lets do an example with failing sensors.

 1: namespace expressions {
 2: 
 3:   auto get_check_sensor(Security::Sensors& failedSensors)
 4:   {
 5: 
 6:     /////////////////////////////////////////////////////////////////////////////
 7:     //                      Check if sensors are turned on                     //
 8:     /////////////////////////////////////////////////////////////////////////////
 9: 
10:     return ([&failedSensors](auto security) {
11:       auto it = std::copy_if(std::begin(security.sensors_), std::end(security.sensors_),
12:                              std::begin(failedSensors),
13:                    [](auto const& sensor) {
14:                      return !sensor.verify();
15:                    });
16:       failedSensors.resize(std::distance(std::begin(failedSensors), it));
17:       return failedSensors.empty();
18: 
19:     });
20:   }
21: 
22: }  // expressions

This of course is one of doing, don't actually know if its the fastests. One thing not to forget is to make sure that failedSensors are large enough to fit all the sensors (if for some reason all sensors fails).

We can now use this togheter with our operators in the following way.

 1: Security::Sensors failedSensor(security.sensors_.size());
 2: auto all_system_on =  expressions::get_check_sensor(failedSensor) && expressions::check_camera_power;
 3: 
 4: if( !all_system_on(security) )
 5: {
 6:   for( const auto& sensor : failedSensor )
 7:   {
 8:     console->error(" Sensor \"{}\" failed  ", sensor.name_);
 9:   }
10: }

The exact same way can be done with cameras. This example shows that its possible to retrive output from the checks, its a side effect, but can be pretty useful. One thing to remeber is that failedSensor must outlive the call to the check-function.

3.2. Pipe values

Lets say I want to print out all the cameras which has are turned off. That is having power_ set to false.

Something like:

1: auto fun = expressions::get_all_cameras_prop(false) >>= expressions::print;
2: fun(security.cameras_.begin(), security.cameras_.end());

Lets first define the function which is neede to filter out, that is expressions::get_all_cameras_prop

1: auto get_all_item_prop( bool prop)
2: {
3:   return [prop](auto const& item) {return (item.power_ == prop ); };
4: }

This is hopefully self explanatory, all the cameras with the capture prop will return true, and therefor passed on to the next lambda.

So the next lambda will is the verb of the sentence. In this case we will use a print function.

1: auto print = [](auto const &item) {
2:     fmt::print("|{:<10}|{:>30}|\n","Item", item.name_);
3: };

The only things that is missing is to add the operator which is needed >>= (well, I don't know if anything else is better). Since we want to use this operator for other functions too, to iterate over ranges, we define it as taking in iterators instead.

 1: 
 2: namespace expressions {
 3: 
 4: template<class Expr1, class Expr2>
 5: auto operator>>=(Expr1&& lhs , Expr2&& rhs)
 6: {
 7:   return [Lhs = std::move(lhs), Rhs = std::move(rhs)](auto it_start,auto it_end)
 8:   {
 9:     std::for_each(it_start, it_end,
10:                   [&Lhs,&Rhs](auto const& item) {
11:                     if(Lhs(item))
12:                     {
13:                       Rhs(item);
14:                     }
15: 
16:                   });
17:   };
18: };
19: 
20: }  // expressions

In this case we can do the following:

 1: int main()
 2: {
 3:   Security security{
 4:     {Camera{Pos{1, 2}, false, "Camera 1"},
 5:      Camera{Pos{2, 3}, true, "Camera 2"},
 6:      Camera{Pos{3, 4}, false, "Camera 3"}
 7:     },
 8:     {Sensor{Pos{1, 2}, false, "Sensor 1"},
 9:      Sensor{Pos{2, 3}, true, "Sensor 2"},
10:      Sensor{
11:         Pos{3, 4}, false, "Sensor 3"
12:       }
13:     }
14:   };
15: 
16:   auto console = spdlog::stdout_color_mt("console");
17: 
18:   console->debug("Starting PassOn");
19: 
20:   {
21:     //auto f = expressions::get_all_cameras_prop(true) >>= expressions::get_print("Camera");
22:     fmt::print("|{:-^10}|{:-^30}|\n", "-",  "-");
23: 
24:     auto f = expressions::get_all_cameras_prop(true) >>= expressions::print;
25:     f(security.cameras_.begin(),security.cameras_.end());
26: 
27:     fmt::print("|{:-<10}|{:->30}|\n", "-",  "-");
28:     auto g = expressions::get_all_sensors_prop(false) >>= expressions::print;
29:     g(security.sensors_.begin(),security.sensors_.end());
30:     fmt::print("|{:-<10}|{:->30}|\n", "-",  "-");
31:   }
32: 
33: 
34:   return 0;
35: }

And when we run this program we get the following output

|----------|------------------------------|
|Item      |                      Camera 2|
|----------|------------------------------|
|Item      |                      Sensor 1|
|Item      |                      Sensor 3|
|----------|------------------------------|

4. Inherit from lambdas

template< class T1>
struct Calle : T1
{
    Calle(T1 f): T1(std::move(f)){}
    using T1::operator();
};

int main()
{
  auto l = [](int i){ std::cout << i << '\n'; };

  Calle c = Calle{l};
  c(12);

}

Author: Carl Olsen

Created: 2022-06-11 Sat 19:33