switch statement for an std::pair? - c++11

I want to switch over possible values of two integers, or in another case two bools. For the sake of discussion, suppose I've done
auto mypair = std::make_pair(foo, bar);
How can I achieve the equivalent of
switch(mypair) {
case make_pair(true, false): cout << "true and false"; break;
case make_pair(false, true) cout << "false and true"; break;
case default: cout << "something else";
}
with C++11? (C++14/17 also relevant if that helps)?

You can only switch on an integral type, but if you can devise a function to map your pair (or any complex type) to an integral type, you can declare it as constexpr (C++11) to indicate it can be resolved at compile time. Then it is acceptable as a case expression.
Simple example:
enum Action { peel, eat, juice };
enum Fruit { apple, orange, banana };
constexpr unsigned int switch_pair(Action a, Fruit f) {
return (a << 16) + f;
}
Then define the switch like this:
switch(switch_pair(mypair.first,mypair.second))
{
case switch_pair(peel,apple): std::cout << "Peeling an apple" << std::endl; break;
case switch_pair(eat,apple): std::cout << "Eating an apple" << std::endl; break;
case switch_pair(juice,apple): std::cout << "Juicing an apple" << std::endl; break;
default:
throw std::runtime_error("We only have apples!");
}

C++'s switch statement doesn't have the pattern matching power of many other languages. You'll need to take a slightly different approach.
Here's a possibility I threw together:
pair_switch(my_pair,
std::make_tuple(true, false, []{ std::cout << "true and false"; }),
std::make_tuple(false, true, []{ std::cout << "false and true"; }));
You supply a std::pair<bool,bool> and a set of cases as std::tuples, where the first two elements match the pair you pass in and the third element is a function to call for that case.
The implementation has a few template tricks, but should be pretty usable:
template <typename... Ts>
void pair_switch(std::pair<bool,bool> pair, Ts&&... ts) {
//A table for the cases
std::array<std::function<void()>, 4> table {};
//Fill in the cases
(void)std::initializer_list<int> {
(table[std::get<0>(ts)*2 + std::get<1>(ts)] = std::get<2>(ts), 0)...
};
//Get the function to call out of the table
auto& func = table[pair.first*2 + pair.second];
//If there is a function there, call it
if (func) {
func();
//Otherwise, throw an exception
} else {
throw std::runtime_error("No such case");
}
}
Live Demo

Related

c++ : unordered map with pair of string_viewes

Here is a code snippet I have :
struct PairHasher {
size_t operator()(const std::pair<std::string_view, std::string_view>& stop_stop) const {
return hasher(stop_stop.first) + 37*hasher(stop_stop.second);
}
std::hash<std::string_view> hasher;
};
BOOST_FIXTURE_TEST_CASE(unordered_map_string_view_pair_must_be_ok, TestCaseStartStopMessager)
{
const std::vector<std::string> from_stops = {"from_0", "from_1", "from_2"};
const std::vector<std::string> to_stops = {"to_0", "to_1", "to_2"};
std::unordered_map<std::pair<std::string_view, std::string_view>, std::int32_t, TransportCatalogue::PairHasher> distance_between_stops;
for ( std::size_t idx = 0; idx < from_stops.size(); ++idx) {
std::cout << from_stops[idx] << " : " << to_stops[idx] << std::endl;
distance_between_stops[std::pair(from_stops[idx], to_stops[idx])] = idx;
}
std::cout << "MAP CONTENT :" << std::endl;
for (auto const& x : distance_between_stops)
{
std::cout << x.first.first << " : " << x.first.second << std::endl;
}
}
I expect to see 3 pairs inside the container, but there is only 1 concerning to the output :
MAP CONTENT :
from_2 : to_2
So, where are two more pair lost? What am I doing wrong?
Moving my comment to an answer.
This is pretty sneaky. I noticed in Compiler Explorer that changing:
distance_between_stops[std::pair(from_stops[idx], to_stops[idx])] = idx;
to
distance_between_stops[std::pair(std::string_view{from_stops[idx]}, std::string_view{to_stops[idx]})] = idx;
fixes the bug. This hints that the problem lies in some implicit string -> string_view conversion. And indeed that is the case, but it is hidden behind one extra layer.
std::pair(from_stops[idx], to_stops[idx]) creates a std::pair<std::string, std::string>, but distance_between_stops requires a std::pair<std::string_view, std::string_view>. When we insert values into the map, this conversion happens implicitly via overload #5 here:
template <class U1, class U2>
constexpr pair(pair<U1, U2>&& p);
Initializes first with std::forward<U1>(p.first) and second with std::forward<U2>(p.second).
This constructor participates in overload resolution if and only if std::is_constructible_v<first_type, U1&&> and std::is_constructible_v<second_type, U2&&> are both true.
This constructor is explicit if and only if std::is_convertible_v<U1&&, first_type> is false or std::is_convertible_v<U2&&, second_type> is false.
(For reference, std::is_constructible_v<std::string_view, std::string&&> and std::is_convertible_v<std::string&&, std::string_view> are both true, so we know this overload is viable and implicit.)
See the problem yet? When we use the map's operator[], it has to do an implicit conversion to create a key with the proper type. This implicit conversion constructs a pair of string_views that are viewing the temporary memory from the local pair of strings, not the underlying strings in the vector. In other words, it is conceptually similar to:
std::string_view foo(const std::string& s) {
std::string temp = s + " foo";
return temp;
}
int main() {
std::string_view sv = foo("hello");
std::cout << sv << "\n";
}
Clang emits a warning for this small example, but not OP's full example, which is unfortunate:
warning: address of stack memory associated with local variable 'temp' returned [-Wreturn-stack-address]
return temp;
^~~~

C++ Struct attributes can change within function, but remain unchanged outside scope of function

I'm working on a self imposed challenge which involves implementing a linked list and an append function for it, which is giving me issues seemingly related to variable scope.
The append function loops through each link element until it reads a NULL value and then changes the data value associated with that link to the function input. The test outputs within the function seem to show it is working as intended, but when performing the same test outside the function, even after it is called gives a different output.
template <class T>
struct atom{
T data;
atom<T>* link = NULL;
};
template <class T>
void append_LL(atom<T> first, T input_data){
atom<T>* current_node = &first;
atom<T>* next_node = current_node->link;
int i = 0;
while (i < 4 && next_node != NULL) {
current_node = next_node;
next_node = next_node->link;
i ++;
}
current_node->data = input_data;
current_node->link = (atom<T>*)malloc(sizeof(atom<T>));
cout << "leaving node as: " << current_node->data << endl; //outputs 5
cout << "input nodes data: " << first.data << endl; //outputs 5
}
int main() {
int dd = 5;
atom<int> linked_list;
linked_list.data = 999;
append_LL(linked_list, dd);
cout << linked_list.data << endl; //outputs 999
}
Because you are not sending the same atom. You see the program is making a copy of the linked_list in the main function and sending that copy to the function.
If you want to modify the same linked_list then change
void append_LL(atom<T> first, T input_data){
to
void append_LL(atom<T> &first, T input_data){
That way you are sending the really atom not a copy of it.

Using .sum() and += on std::valarray<T>

I am using the type std::valarray<std::valarray<double>> and wish to sum each of the contained valarrays element wise, to leave a std::valarray<double>.
The C++ documentation states that the operator .sum() can be applied to std::valarray<T> so long as the operator += is defined for type T. My code below (method1) tries to apply this to std::valarray<std::valarray<double>>, but the result appears to be nonsense.
However if I perform this manually, using the += operator (method2), I get the result I want. But the fact that method2 works seems to imply that the operator += is defined for the type std::valarray<double>, and hence that method1, using .sum(). should work. I really can't understand what is happening here...
My code:
#include <iostream>
#include <valarray>
// Attempt to use .sum() operator
std::valarray<double> method1(const std::valarray<std::valarray<double>>& data) {
return data.sum();
}
// Manual summation using += operator
std::valarray<double> method2(const std::valarray<std::valarray<double>>& data) {
std::valarray<double> sum(data[0].size());
for (size_t i{0}; i < data.size(); i++) {
sum += data[i];
}
return sum;
}
// Display size and elements
void showData(const std::valarray<double> data) {
std::cout << "Size = " << data.size() << "\n";
std::cout << "Data = ";
for (size_t i{0}; i < data.size(); i++) {
std::cout << data[i] << " ";
}
std::cout << "\n\n";
}
int main() {
std::valarray<std::valarray<double>> data{{1,2},{3,4}};
showData(method1(data));
showData(method2(data));
}
My output:
Size = 0
Data =
Size = 2
Data = 4 6
The sum method of std::valarray requires operator+= to be defined for its value type (in your case, std::valarray), but std::valarray also requires it to be default-constructible (from the "Numeric" concept requirement).
This allows the sum method to work without operator+, by first default-constructing an element, and then adding each contained element with operator+=.
Although it isn't defined anywhere, as far as I know, it probably works something like this.
T sum() const {
T result;
for (auto& it : elements) {
result += it;
}
return result;
}
The problem with a valarray of valarrays (std::valarray<std::valarray>) is that a default-constructed valarray is empty. And when operator+= is applied with an empty valarray and a non-empty one, it results in undefined behavior ("The behavior is undefined if size() != v.size()"). What you are likely to get is an empty valarray as a result (but you could potentially get anything).
What you could use instead is std::accumulate. It requires an initial value as third parameter, which takes care of the problem.
std::accumulate(std::begin(data), std::end(data), std::valarray<double>(data[0].size()))
Live on Coliru.
PS: don't ask me why std::valarray has no method begin and end.

return type of decltype(*this)

I think I might have missed the subtlety in move construction because when I change the line Foo copy(*this); to decltype(*this) copy(*this);, I am thoroughly surprised by the output.
I checked it against, clang++-3.5 and g++-4.9, with the same behavior.
Would really appreciate a quick tip from the C++11 guru.
Update: Just forced the compiler to print the type of decltype(*this), it is actually a reference type i.e. Foo&.
class Foo {
public:
Foo(int a): val(a) {}
operator int() { return val; }
auto& operator++() {
val++;
return *this;
}
auto operator++(int) {
//Foo copy(*this);
decltype(*this) copy(*this);
++(*this);
return copy;
}
private:
int val;
};
int main()
{
Foo foo=1;
cout << "foo++ = " << foo++ << "\n";
cout << "foo++ = " << foo++ << "\n";
cout << "foo = " << foo << "\n";
return 0;
}
The output
foo++ = 2
foo++ = 3
foo = 3
There seems to be a confusion as to why decltyp(*this) is Foo& and not Foo in your case. Firstly, think about dereferencing a pointer always resulting in a reference to the pointed to object.
temp = *ptr // this would work if dereferencing returned by value or by reference
*ptr = expr // this would only work if dereferencing results in a reference.
Now decltype(expr) always gives you exactly the same type as the expr. For you *this is of type Foo&.
If you want type deduction without it resulting in a reference use auto instead of decltype, so:
auto copy(*this);
instead of
decltype(*this) copy(*this);
Also I don't know why your question is talking about move construction so much as there is no move involved anywhere.

How does return by rvalue reference work?

Just when I thought I kind of understand rvalue reference, I ran into this problem. The code is probably unnecessarily long, but the idea is quite simple. There is a main() function, and returnRValueRef() function.
#include <iostream>
#define NV(x) "[" << #x << "=" << (x) << "]"
#define log(x) cout << __FILE__ << ":" << __LINE__ << " " << x << endl
using namespace std;
class AClass {
public:
int a_;
AClass() : a_(0) {
log("inside default constructor");
}
AClass(int aa) : a_(aa) {
log("inside constructor");
}
int getInt() const {
return a_;
}
void setInt(int a) {
a_ = a;
}
AClass(AClass const & other) : a_(other.a_) {
log("inside copy constructor");
}
AClass & operator=(AClass const & rhs) {
log("inside assignment operator" << "left value" << NV(a_) << "right value" << NV(rhs.a_));
a_ = rhs.a_;
return *this;
}
AClass & operator=(AClass && rhs) {
log("inside assignment operator (rvalue ref)" << "left" << NV(a_) << "right" << NV(rhs.a_));
a_ = rhs.a_;
return *this;
}
};
AClass && returnRValueRef() {
AClass a1(4);
return move(a1);
}
int main() {
AClass a;
a = returnRValueRef();
}
Okay, I would expect this code to first print "inside default constructor" (for a), then "inside constructor" (for a1), and then assignment operator message with rhs.a_ = 4. But the output is
testcpp/return_rvalue_ref.cpp:14 inside default constructor
testcpp/return_rvalue_ref.cpp:17 inside constructor
testcpp/return_rvalue_ref.cpp:39 inside assignment operator (rvalue ref)left[a_=0]right[rhs.a_=0]
Can somebody explain why the last line in the output prints right[rhs.a_=0] instead of right[rhs.a_=4]? I thought the move() just makes lvalue into rvalue without changing its contents. But I am clearly missing something.
Thanks so much for your help. :-)
Edit: I think I know what might be going on. May be the destructor for a1 in function returnRValueRef() is being called when it goes out of scope (even if it is turned into rvalue), and after that the memory location for a1 (or rvalue reference for it) contains undefined stuff! Not sure if that is what is happening, but seems plausible.
An rvalue reference is still a reference. In your case, you are referencing the local variable that has been destructed. Therefore it is undefined behavior to access the members. What you want to do is to return the object:
AClass returnRValueRef() {
AClass a1(4);
return move(a1);
}
However, a move happens automatically with a local variable, so you really only need to do this:
AClass returnRValueRef() {
AClass a1(4);
return a1;
}

Resources