C++ code refactoring for if scenario - c++

I'm looking to refactor the c++ code for if conditions which will reduce the number of lines of code as well as it should have min complexity.
Here is the example:
if (xyz->a != cmd->aa)
{
xyz->a = cmd->aa;
obj->isFound = true; //common code for all ifs
}
if (xyz->b != cmd->bb)
{
xyz->b = cmd->bb;
obj->isFound = true;
}
And so on.. Here a, b, aa, bb are defined as a struct element.
Another example having if condition with arrays:
if (abc->r16[0] != cmd->r0m)
{
abc>r16[0] = cmd->r0m;
obj->isFound = true; //some common code for all ifs
}
if (abc->r16[1] != cmd->r1m)
{
abc>r16[1] = cmd->r1m;
obj->isFound = true; //some common code for all ifs
}
And so on for r16[0] to r16[15]. Here r16[15] is defined inside struct.
Last scenario is for if condition with multidimensional array:
if (pqr->c_0_15_r_0_15[0][0] != cmd->obj0000)
{
pqr->c_0_15_r_0_15[0][0] = cmd->obj0000
obj->isFound = true; //some common code for all ifs
}
if (pqr->c_0_15_r_0_15[1][0] != cmd->obj0100)
{
pqr->c_0_15_r_0_15[1][0] = cmd->obj0100
obj->isFound = true; //some common code for all ifs
}
if (pqr->c_0_15_r_0_15[2][0] != cmd->obj0000)
{
pqr->c_0_15_r_0_15[2][0] = cmd->obj0200
obj->isFound = true; //some common code for all ifs
}
Here c_0_15_r_0_15[2][0] will go through [0][0] to [15][0] and then [0][1] to [15][1] and so on...
For all such if condition scenario there will me 100's of if statements which should be reduced. How can we refactor such code?

First, find the duplication in your code. As you already aware - the following scheme is duplicated many times:
if (a != b)
{
a = b;
found = true;
}
So - encapsulate this in a function (template if there are multiple types:
template <typename Dst, typename Src>
inline void updateIfNeeded(Dst& dst, const Src& src, bool& wasNeeded)
{
if (dst != src)
{
dst = src;
wasNeeded = true;
}
}
See - how it helps:
updateIfNeeded(abc->r16[0], cmd->r0m, obj->isFound);
updateIfNeeded(abc->r16[1], cmd->r1m, obj->isFound);
// ...
updateIfNeeded(pqr->c_0_15_r_0_15[0][0], cmd->obj0000, obj->isFound);
updateIfNeeded(pqr->c_0_15_r_0_15[1][0], cmd->obj0100, obj->isFound);
// ...
So far - reduction is quite big - and it is much more readable what is going on in this code.
What I see more - obj->isFound is repeating many times - maybe - some encapsulation in a class this time:
class Updater
{
public:
bool anyUpdateWasNeeded = false;
template <typename Dst, typename Src>
void updateIfNeeded(Dst& dst, const Src& src)
{
if (dst != src)
{
dst = src;
anyUpdateWasNeeded = true;
}
}
};
You see - not needed to pass obj->isFound so many times:
Updater upd;
upd.updateIfNeeded(abc->r16[0], cmd->r0m);
upd.updateIfNeeded(abc->r16[1], cmd->r1m);
// ...
upd.updateIfNeeded(pqr->c_0_15_r_0_15[0][0], cmd->obj0000);
upd.updateIfNeeded(pqr->c_0_15_r_0_15[1][0], cmd->obj0100);
// ...
obj->isFound = upd.anyUpdateWasNeeded;
Frankly, at this point I would consider to use preprocessor - I mean to shorten all these arrays update - this is because on left you have arrays - on right you have many fields of similar names ("indexed" names...?)
But using preprocessor is bad style - let try to update arrays with pure C++:
class Updater
{
public:
bool anyUpdateWasNeeded = false;
template <typename Dst, typename Src>
void updateIfNeeded(Dst& dst, const Src& src);
template <typename Dst, typename Src1, typename ...Src>
void updateArrayIfNeeded(Dst* dst, const Src1& src1, const Src& ...src)
{
updateIfNeeded(*dst, src1);
updateArrayIfNeeded(dst + 1, src...);
}
template <typename Dst>
void updateArrayIfNeeded(Dst* dst)
{
// nothing left
}
};
So - this is what left:
upd.updateArrayIfNeeded(abc->r16, cmd->r0m, cmd->r1m, ....);
upd.updateArrayIfNeeded(pqr->c_0_15_r_0_15[0], cmd->obj0000, cmd->obj0001, ...);
upd.updateArrayIfNeeded(pqr->c_0_15_r_0_15[1], cmd->obj0100, ...);
// ...
obj->isFound = upd.anyUpdateWasNeeded;
And so on...

You can put it in some function, for a start:
template<typename Lhs, typename Rhs>
void flagSetIfNEq(Lhs & lhs, Rhs const & rhs, bool & flag) {
if (lhs != rhs) {
lhs = rhs;
flag = true;
}
}
// call it like
flagSetIfNEq(xyz->a, uvw->aa, obj->found)!
I'm assuming that found in your code is of type bool.
Though if you have that "100s of times" in your code you probably should consider a more aggressive refactoring.
And give that function a better name.

Related

How can i delete an element from a map into a vector

I'm currently trying for a game project to get rid of a dangling reference when a plane crashes before reaching the terminal it was booked to reach.
I would like to go through <algorithm> functions only to better understand how they work.
At the moment I've tried going through the map that contains all the planes associated with a terminal, comparing it with the list of all the planes and checking if a plane that is in the map is no longer in the vector then delete it from the map to free the associated terminal.
void remove_crashed_aircraft(std::unordered_map<const Aircraft*, size_t>& reserved_terminals, std::vector<std::unique_ptr<Aircraft>>& aircrafts)
{
auto it = std::all_of(reserved_terminals.begin(), reserved_terminals.end(),
[aircrafts](const Aircraft* a1){ return std::find_if(aircrafts.begin(), aircrafts.end(),
[a1](std::unique_ptr<Aircraft> a2){ return a1==a2.get();});});
reserved_terminals.erase(it);
}
And this is my Aircraft class:
class Aircraft : public GL::Displayable, public GL::DynamicObject
{
private:
const AircraftType& type;
const std::string flight_number;
Point3D pos, speed; // note: the speed should always be normalized to length 'speed'
WaypointQueue waypoints = {};
Tower& control;
bool landing_gear_deployed = false; // is the landing gear deployed?
bool is_at_terminal = false;
int fuel = 0;
// turn the aircraft to arrive at the next waypoint
// try to facilitate reaching the waypoint after the next by facing the
// right way to this end, we try to face the point Z on the line spanned by
// the next two waypoints such that Z's distance to the next waypoint is
// half our distance so: |w1 - pos| = d and [w1 - w2].normalize() = W and Z
// = w1 + W*d/2
void turn_to_waypoint();
void turn(Point3D direction);
// select the correct tile in the plane texture (series of 8 sprites facing
// [North, NW, W, SW, S, SE, E, NE])
unsigned int get_speed_octant() const;
// when we arrive at a terminal, signal the tower
void arrive_at_terminal();
// deploy and retract landing gear depending on next waypoints
void operate_landing_gear();
void add_waypoint(const Waypoint& wp, const bool front);
bool is_on_ground() const { return pos.z() < DISTANCE_THRESHOLD; }
float max_speed() const { return is_on_ground() ? type.max_ground_speed : type.max_air_speed; }
bool is_paused = false;
Aircraft(const Aircraft&) = delete;
Aircraft& operator=(const Aircraft&) = delete;
public:
Aircraft(const AircraftType& type_, const std::string_view& flight_number_, const Point3D& pos_,
const Point3D& speed_, Tower& control_, int fuel_) :
GL::Displayable { pos_.x() + pos_.y() },
type { type_ },
flight_number { flight_number_ },
pos { pos_ },
speed { speed_ },
control { control_ },
fuel { fuel_ }
{
speed.cap_length(max_speed());
}
const std::string& get_flight_num() const { return flight_number; }
float distance_to(const Point3D& p) const { return pos.distance_to(p); }
bool is_circling() const
{
if (!has_terminal() && !is_at_terminal)
{
return true;
}
return false;
}
bool has_terminal() const
{
if (waypoints.empty())
{
return false;
}
else
{
return waypoints.back().type == wp_terminal;
}
}
bool is_low_on_fuel() const
{
if (fuel<200)
{
return true;
}
else
{
return false;
}
}
void display() const override;
bool move() override;
void refill(int& fuel_stock);
friend class Tower;
friend class AircraftManager;
};
The code of the function generates errors that I can't understand unfortunately.
use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Aircraft; _Dp = std::default_delete<Aircraft>]’
static assertion failed: result type must be constructible from value type of input range
If anyone has an idea of how I can achieve this, I would be very grateful!
First, you have to use std::remove_if, because std::all_of returns bool, not iterator. But std::remove_if does what you want, it removes all instances that match bool predicate.
Second, your compile errors appear because you pass by value everywhere, so instead of (std::unique_ptr<Aircraft> a2) pass reference (std::unique_ptr<Aircraft> const & a2), instead [aircrafts] and [a1] pass [&aircrafts] and [&a1].
Third, inner predicate should not just return result of std::find_if (which returns iterator) but return bool, i.e. compare result of find_if to aircrafts.end().
Speed-wise your algorithm can be optimized if needed, for that you have to convert std::vector to std::unordered_set and then iterate through first map and check containment inside second set. If you don't have too many elements and speed is not of much importance then your algorithm is alright.
Final working code below:
void remove_crashed_aircraft(
std::unordered_map<const Aircraft*, size_t>& reserved_terminals,
std::vector<std::unique_ptr<Aircraft>>& aircrafts
) {
std::remove_if(reserved_terminals.begin(), reserved_terminals.end(),
[&aircrafts](auto const & a1){
return std::find_if(aircrafts.begin(), aircrafts.end(),
[&a1](auto const & a2){ return a1.first == a2.get(); })
== aircrafts.end();
}
);
}

Why am I getting a race condition?

I'm trying to combine multiple CGAL meshes into one single geometry.
I have the following sequential code that works perfectly fine:
while (m_toCombine.size() > 1) {
auto mesh1 = m_toCombine.front();
m_toCombine.pop_front();
auto mesh2 = m_toCombine.front();
m_toCombine.pop_front();
bool result = CGAL::Polygon_mesh_processing::corefine_and_compute_union(mesh1, mesh2, mesh2);
m_toCombine.push_back(mesh2);
}
Where m_toCombine is a std::list<Triangle_mesh_exact>.
Triangle_mesh_exact is a type of CGAL mesh (triangulated polyhedron geometry). But I don't think it's really relevant to the problem.
Unfortunately, this process is way too slow for my intended application, so I decided to use the "divide to conquer" concept and combine meshes in a parallel fashion:
class Combiner
{
public:
Combiner(const std::list<Triangle_mesh_exact>& toCombine) :
m_toCombine(toCombine) {};
~Combiner() {};
Triangle_mesh_exact combineMeshes();
void combineMeshes2();
private:
std::mutex m_listMutex, m_threadListMutex;
std::mutex m_eventLock;
std::list<MiniThread> m_threads;
std::list<Triangle_mesh_exact> m_toCombine;
std::condition_variable m_eventSignal;
std::atomic<bool> m_done = false;
//void poll(int threadListIndex);
};
Triangle_mesh_exact Combiner::combineMeshes()
{
std::unique_lock<std::mutex> uniqueLock(m_eventLock, std::defer_lock);
int runningCount = 0, finishedCount = 0;
int toCombineCount = m_toCombine.size();
bool stillRunning = false;
bool stillCombining = true;
while (stillCombining || stillRunning) {
uniqueLock.lock();
//std::lock_guard<std::mutex> lock(m_listMutex);
m_listMutex.lock();
Triangle_mesh_exact mesh1 = std::move(m_toCombine.front());
m_toCombine.pop_front();
toCombineCount--;
Triangle_mesh_exact mesh2 = std::move(m_toCombine.front());
m_toCombine.pop_front();
toCombineCount--;
m_listMutex.unlock();
runningCount++;
auto thread = new std::thread([&, this, mesh1, mesh2]() mutable {
//m_listMutex.lock();
CGAL::Polygon_mesh_processing::corefine_and_compute_union(mesh1, mesh2, mesh2);
std::lock_guard<std::mutex> lock(m_listMutex);
m_toCombine.push_back(mesh2);
toCombineCount++;
finishedCount++;
m_eventSignal.notify_one();
//m_listMutex.unlock();
});
thread->detach();
while (toCombineCount < 2 && runningCount != finishedCount) {
m_eventSignal.wait(uniqueLock);
}
stillRunning = runningCount != finishedCount;
stillCombining = toCombineCount >= 2;
uniqueLock.unlock();
}
return m_toCombine.front();
}
Unfortunately, despite being extra careful, I'm getting crashes of memory access violation or errors related to either mesh1 or mesh2 destructors.
Am I missing something?
Instead complicating things check capability of standard library:
std::reduce - cppreference.com
Triangle_mesh_exact combine(Triangle_mesh_exact& a, Triangle_mesh_exact& b)
{
auto success = CGAL::Polygon_mesh_processing::corefine_and_compute_union(a, b, b);
if (!success) throw my_combine_exception{};
return b;
}
Triangle_mesh_exact combineAll()
{
if (m_toCombine.size() == 1) return m_toCombine.front();
if (m_toCombine.empty()) throw std::invalid_argument("");
return std::reduce(std::execution::par,
m_toCombine.begin() + 1, m_toCombine.end(),
m_toCombine.front(), combine);
}

Function returning a container containing specific elements of input container

I have a vector or list of which I only want to apply code to specific elements. E.g.
class Container : public std::vector<Element*>
Or
class Container : public std::list<Element*>
And:
Container newContainer = inputContainer.Get(IsSomething);
if (!newContainer.empty()) {
for (Element* const el: newContainer ) {
[some stuff]
}
} else {
for (Element* const el : inputContainer) {
[some stuff]
}
}
I've written a member function Get() as follows.
template<typename Fn>
auto Container::Get(const Fn& fn) const {
Container output;
std::copy_if(cbegin(), cend(), std::inserter(output, output.end()), fn);
return output;
}
and IsSomething would be a lambda, e.g.
auto IsSomething= [](Element const* const el)->bool { return el->someBool; };
From performance perspective: Is this a good approach? Or would it be better to copy and remove?
template<typename Fn>
auto Container::Get(const Fn& fn) const {
Container output(*this);
output.erase(std::remove_if(output.begin(), output.end(), fn), end(output));
return output;
}
Or is there a better approach anyhow?
edit: different example
As my previous example can be written in a better way, let's show a different example:
while (!(container2 = container1.Get(IsSomething)).empty()&&TimesFooCalled<SomeValue)
{
Container container3(container2.Get(IsSomething));
if (!container3.empty()) {
Foo(*container3.BestElement());
} else {
Foo(*container2.BestElement());
}
}
Not answering your direct question, but note that you can implement the original algorithm without copying anything. Something like this:
bool found = false;
for (Element* const el: inputContainer) {
if (IsSomething(el)) {
found = true;
[some stuff]
}
}
if (!found) {
for (Element* const el : inputContainer) {
[some stuff]
}
}
The usual pattern that I use is something like this:
for(auto const * item : inputContainer) if(IsSomething(item)) {
// Do stuff with item
}
This is usually good enough, so other approaches seem overkill.
For better performance it is always better not to copy or remove elements from the list you get. In my experience it's even faster if you only go through the list once, for caching reasons. So here is what I would do to find one or the other "best" value from a list:
auto const isBetter = std::greater<Element>();
Element const * best = nullptr, const * alt_best = nullptr;
for(Element const * current : inputContainer) {
if(IsSomething(current)) {
if(!best || isBetter(*best, *current)) best = current;
} else {
if(!alt_best || isBetter(*alt_best, *current)) alt_best = current;
}
}
if(best) {
// do something with best
} else if(alt_best) {
// do something with alt_best
} else {
// empty list
}
If you find yourself doing this a lot or you want to make this part of your class's interface you could consider writing an iterator that skips elements you don't like.
If you actually want to remove the item from the list, you could do something like this:
inputContainer.erase(std::remove_if(std::begin(inputContainer), std::end(inputContainer),
[](Element const *item) {
if(IsSomething(item)) {
// Do something with item
return true;
}
return false;
}
));

Have enum value be equivalent to many others

I need to use an enum of various values, in this case various building pieces. Most of these are unique, but there a few that I'd like to be equivalent. I mean as follows:
enum class EPiece: uint8 {
Ceiling,
Table,
Door,
WestWall,
NorthWall,
SouthWall,
EastWall,
Wall,
Floor
};
And I'd like to Wall == WestWall to be true, as well as Wall == NorthWall, etc. However, WestWall == NorthWall is false.
Why I am doing this is because I am making a game where various pieces have a definition based off of what they are/where they are. The player has to place various pieces in a predefined order. The player first has to place a NorthWall piece. They will have available various pieces, and will have to select a Wall piece, and have to attempt to place it on a NorthWall piece. The game checks if the two are equivalent (in this case true), and if the current piece to place is NorthWall. If they attempt to place it on a WestWall piece it should fail since it's not that stage yet.
I thought of doing this through flags, doing something like
WestWall = 0x01,
NorthWall = 0x02,
SouthWall = 0x04,
EastWall = 0x08,
Wall = WestWall | NorthWall | SouthWall | EastWall
and checking by doing something like:
// SelectedPiece is the Piece the Player selected and is attempting to place
// PlacedOnPiece is the Piece that we are attempting to place on top of
// CurrentPieceToPlace is what Piece we are supposed to place at this stage
if ((CurrentPieceToPlace == PlacedOnPiece) && (SelectedPiece & PlacedOnPiece != 0)) {
}
The thing is, I have a lot of pieces and my understanding is to make the flags work I have to use powers of two. That means if I use uint32 I could have a max of 32 Pieces, and I don't want to be limited by that. I might only need around 20, but I don't want to get stuck.
Any suggestions? At this point I need to use an enum, so I can't try a different type.
I'd advise against overloading == to have that meaning. == is usually transitive (if A==B and B==C, then A==C), and if it fails to be transitive otherwise "sane" code will break.
Start with your enum:
enum class EPiece: uint8 {
Ceiling,
Table,
Door,
WestWall,
NorthWall,
SouthWall,
EastWall,
Wall,
Floor
};
Now define an can_be_used_as_a relationship.
bool can_be_used_as_a( EPiece x, EPiece used_as_a_y ) {
if (x==y) return true;
switch(x) {
case Wall: {
switch(used_as_a_y) {
case WestWall:
case EastWall:
case NorthWall:
case EastWall:
return true;
default: break;
}
}
default: break;
}
switch(used_as_a_y) {
case Wall: {
switch(x) {
case WestWall:
case EastWall:
case NorthWall:
case EastWall:
return true;
default: break;
}
}
default: break;
}
return false;
}
now can_be_used_as_a( WestWall, Wall ) is true because a WestWall can be used as a Wall. And similarly, Wall can be used as a WestWall. But a WestWall cannot be used as a EastWall.
If you want slightly cleaner syntax, we can write a named operator:
namespace named_operator {
template<class D>struct make_operator{make_operator(){}};
template<class T, char, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
-> decltype( invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
{
return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
for the 12 line named operator library, used like:
struct used_as_a_tag{};
static const named_operator::make_operator<used_as_a_tag> can_use_as_a;
bool invoke( EPiece x, used_as_a_tag, EPiece y ) {
return can_be_used_as_a(x,y);
}
and now we can do this:
if (x *can_use_as_a* y) {
}
with the operator occurring between the left and right operands. But this might be going too far.
Finally, consider using enum class instead of enum.
You're going in the right direction. Each wall type you have represents a single bit, and that's awesome. Now all you have to do is to combine them in Wall, and to extract them in your checks, so:
WestWall = 0x01, //0b0001
NorthWall = 0x02, //0b0010
SouthWall = 0x04, //0b0100
EastWall = 0x08, //0b1000
Wall = 0xF //0b1111
Now, to check if one value of the enum represents an other value, you should write something like this:
bool isSame(EPiece first, EPiece second)
{
//if they are the same, they are, well... the same.
if(first == second)
return true;
//this only leaves the bits that are present in both values, so
//if the result is different from 0, then second is a part of first, so
//we return true
else if(first & second)
return true;
//if we are here, then first and second are unrelated
return false;
}
You can define your own comparison operators, like this:
bool operator==(EPiece lhs, EPiece rhs)
{
if (int(lhs) == int(EPiece::Wall) &&
(int(rhs) == int(EPiece::NorthWall) ||
int(rhs) == int(EPiece::SouthWall))) // lots more cases...
{
return true;
}
return int(lhs) == int(rhs);
}
Do note that the declaration (though not necessarily the definition) of the above must be visible wherever you expect to compare these things, so you should declare it right alongside the enum declaration.
Here are two slightly different possibilites:
enum {
Flag0 = 1 << 0,
Flag1 = 1 << 1,
Flag2 = 1 << 2,
Flag3 = 1 << 3,
FlagMask = 0x07
}
if (value & FlagMask) // it's got some flags
{ ... }
if (value & Flag3) // Flag3
{ ... }
and
enum {
ItemA0,
ItemABegin = ItemA0,
ItemA1,
ItemA2,
// insert ItemAs here
ItemAEnd,
ItemB0,
ItemBBegin = ItemB0,
ItemB1,
// insert ItemBs here
ItemBEnd,
}
if (ItemABegin <= value && value < ItemAEnd) // it's some ItemA
{ ... }
if (ItemBBegin <= value && value < ItemBEnd) // it's some ItemB
{ ... }
switch (value) { // switch on specific types
case ItemB0: ... break;
case ItemB1: ... break;
}
the second version still encapsulates the idea of an enumeration type.

Can C++ do something like an ML case expression?

So, I've run into this sort of thing a few times in C++ where I'd really like to write something like
case (a,b,c,d) of
(true, true, _, _ ) => expr
| (false, true, _, false) => expr
| ...
But in C++, I invariably end up with something like this:
bool c11 = color1.count(e.first)>0;
bool c21 = color2.count(e.first)>0;
bool c12 = color1.count(e.second)>0;
bool c22 = color2.count(e.second)>0;
// no vertex in this edge is colored
// requeue
if( !(c11||c21||c12||c22) )
{
edges.push(e);
}
// endpoints already same color
// failure condition
else if( (c11&&c12)||(c21&&c22) )
{
results.push_back("NOT BICOLORABLE.");
return true;
}
// nothing to do: nodes are already
// colored and different from one another
else if( (c11&&c22)||(c21&&c12) )
{
}
// first is c1, second is not set
else if( c11 && !(c12||c22) )
{
color2.insert( e.second );
}
// first is c2, second is not set
else if( c21 && !(c12||c22) )
{
color1.insert( e.second );
}
// first is not set, second is c1
else if( !(c11||c21) && c12 )
{
color2.insert( e.first );
}
// first is not set, second is c2
else if( !(c11||c21) && c22 )
{
color1.insert( e.first );
}
else
{
std::cout << "Something went wrong.\n";
}
I'm wondering if there's any way to clean all of those if's and else's up, as it seems especially error prone. It would be even better if it were possible to get the compiler complain like SML does when a case expression (or statement in C++) isn't exhaustive. I realize this question is a bit vague. Maybe, in sum, how would one represent an exhaustive truth table with an arbitrary number of variables in C++ succinctly? Thanks in advance.
I like Alan's solution but I respectfully disagree with his conclusion that it is too complex. If you have access to C++11 it gives you almost all the tools you need. You only need to write one class and two functions:
namespace always {
struct always_eq_t {
};
template <class lhs_t>
bool operator==(lhs_t const&, always_eq_t)
{
return true;
}
template <class rhs_t>
bool operator==(always_eq_t, rhs_t const&)
{
return true;
}
} // always
Then you can write your function in a way relatively similar to ML:
#include <tuple>
#include <iostream>
void f(bool a, bool b, bool c, bool d)
{
always::always_eq_t _;
auto abcd = std::make_tuple(a, b, c, d);
if (abcd == std::make_tuple(true, true, _, _)) {
std::cout << "true, true, _, _\n";
} else if (abcd == std::make_tuple(false, true, _, false)) {
std::cout << "false, true, _, false\n";
} else {
std::cout << "else\n";
}
}
int
main()
{
f(true, true, true, true);
f(false, true, true, false);
return 0;
}
In C++ you often want to consider is there a sensible type that I can create that will help me write my code more easily? Additionally, I think if you have a background in ML you will benefit a lot from examining C++ templates. They are very helpful in applying a functional programming style in C++.
C++ is traditionally oriented to the individual, and you could never do anything resembling the following regardless of syntax.
if ([a,b,c,d] == [true,true,false, false]) {}
The New C++ standard has some stuff that lets you define arrays of constants inline, and so it is possible to define a class that will take in an array as a constructor and support such comparisons. Something like
auto x = multi_val({a,b,c,d});
if (x == multi_val({true, true, false, false}))
{ ... }
else if (x == multi_val(etc.))
But now to do partial matches like with the _, that's not directly supported and you'd have to make your class even more complex to fudge with that, like using a maybe template type and going
multi_val(true, true, maybe<bool>(), maybe<bool>)
This gets into rather heady C++ territory and definitely not what I would do for something so elementary.
For C++11 assuming that you only want to match a fixed number of booleans and can live without the _ pattern matching then [1] (Expand to the number of variables you require).
I'm still working on an alternate solution using templates to match arbitrary types using lambdas or functors for the expressions.
-Edit-
As promised, [2] pattern matching of arbitrary types incl. unspecified values.
Note a couple of caveats:
This code only works with 4 variables (actually my first foray into template metaprogramming). This could very much be improved with variadic templates.
It works but it's not very tidy or well organised. More a proof of concept that would need to be cleaned up before introducing into production code.
I'm not happy with the match function. I was hoping to use initializer lists to pass the expressions to be evaluated and stop on the first match (with the current implementation every matching condition will be executed) - however i couldn't quickly think of how to pass expression matching objects of different types via the single initializer list.
I can't think of a method for either to validate that the truth table is exhaustive.
Cheers,
-nick
[1]
constexpr int match(bool v, int c)
{
return v ? (1 << c) : 0;
}
constexpr int match(bool a, bool b)
{
return match(a, 0) | match(b, 1);
}
int main()
{
int a = true;
int b = false;
switch(match(a, b))
{
case match(false, false):
break;
case match(false, true):
break;
case match(true, false):
break;
case match(true, true):
break;
}
}
[2]
template<typename V1, typename V2, typename V3, typename V4>
class pattern_match_t
{
private:
V1 value_0;
V2 value_1;
V3 value_2;
V4 value_3;
public:
typedef std::function<void(V1, V2, V3, V4)> expr_fn;
template <typename C1, typename C2, typename C3, typename C4>
pattern_match_t<V1, V2, V3, V4>& match(C1 a, C2 b, C3 c, C4 d, expr_fn fn)
{
if(value_0 == a && value_1 == b && value_2 == c && value_3 == d)
fn(value_0, value_1, value_2, value_3);
return *this;
}
pattern_match_t(V1 a, V2 b, V3 c, V4 d)
: value_0(a), value_1(b), value_2(c), value_3(d)
{
}
};
template<typename T>
class unspecified
{};
template<typename T>
constexpr bool operator==(unspecified<T>, const T&)
{
return true;
}
template<typename T>
constexpr bool operator==(const T&, unspecified<T>)
{
return true;
}
template<typename V1, typename V2, typename V3, typename V4>
pattern_match_t<V1, V2, V3, V4> pattern_match(V1 a, V2 b, V3 c, V4 d)
{
return pattern_match_t<V1, V2, V3, V4>(a, b, c, d);
}
int main()
{
bool test_a = true;
std::string test_b = "some value";
bool test_c = false;
bool test_d = true;
pattern_match(test_a, test_b, test_c, test_d)
.match(true, unspecified<std::string>(), false, true, [](bool, std::string, bool, bool)
{
return;
})
.match(true, "some value", false, true, [](bool, std::string, bool, bool)
{
return;
});
}