boost::static_visitor multivisitor non-variant arguments - c++

Is there any inexpensive way to pass an arguments of non-variant types in addition to arguments of variant types, when multivisitor applyed?
What I mean by the term "expensive way" is:
#include <boost/variant.hpp>
#include <iostream>
#include <cstdlib>
struct A {};
struct B {};
enum class C { X, Y };
std::ostream &
operator << (std::ostream & out, C const c)
{
switch (c) {
case C::X : {
return out << "C::X";
}
case C::Y : {
return out << "C::Y";
}
default : {
break;
}
}
throw std::runtime_error("unknown C value");
}
using V = boost::variant< A, B >;
struct S
: boost::static_visitor<>
{
void
operator () (C const c, A const &) const
{
std::cout << c << " A" << std::endl;
}
void
operator () (C const c, B const &) const
{
std::cout << c << " B" << std::endl;
}
};
int main()
{
V const a = A{};
V const b = B{};
using VC = boost::variant< C >;
VC const x = C::X;
VC const y = C::Y;
S const s;
boost::apply_visitor(s, x, a);
boost::apply_visitor(s, y, a);
boost::apply_visitor(s, x, b);
boost::apply_visitor(s, y, b);
return EXIT_SUCCESS;
}
Another expensive way is to make non-static visitor with fileds of required types (or references to required types) and construct instances of such visitor for each set of values of non-variant types every time.

Related

Can't insert non-const value in unordered_map of references

I'm trying to create 2 std::unordered_map, one holds <A, int> and the second one holds <int&, A&>.
I'll explain at the end why I want to do this if you're curious.
My problem is that k_i has value of type std::reference_wrapper, k_i.insert doesn't work. But if I make k_i to have value std::reference_wrapper<const A>, the insert works.
I just can't figure out why is this and I am curious.
<<<<<Edit:
The thing is that find returns std::pair<const Ket, T> as stated by
Eljay in the comments. Because of this, the second std::unordered_map needs to have the value const.
<<<<<
Code:
Compiler: g++ version 10.1
Compile flags: -Wall -Wextra -std=c++20
#include <unordered_map>
#include <iostream>
#include <string>
#include <functional>
class A {
public:
A(const int x) : x(x) {
std::cout << "A::A(const int x) : x(" << x << ")\n";
}
A(const A& a) {
std::cout << "A::A {" << x << "} (const A& a {" << a.x << "} )\n";
x = a.x;
}
A(A&& a) {
std::cout << "A::A {" << x << "} (A&& a {" << a.x << "} )\n";
x = a.x;
}
A& operator=(const A& a) {
std::cout << "A::operator= {" << x << "} (const A& a)\n";
x = a.x;
return *this;
}
A& operator=(A&& a) {
std::cout << "A::operator= {" << x << "} (A&& a)\n";
x = a.x;
return *this;
}
~A() {
std::cout << "A::~A(" << x << ")\n";
}
friend std::ostream& operator<<(std::ostream& os, const A& dt);
int x;
};
std::ostream& operator<<(std::ostream& os, const A& dt) {
return os << dt.x;
}
template <typename K, typename V, typename... args>
void print_um(const std::unordered_map<K, V, args...> &umap) {
for (const auto &[x, y] : umap) {
std::cout << "(" << x << "," << std::ref(y).get() << "); ";
}
std::cout << "\n";
}
template <typename T>
struct MyHash {
std::size_t operator()(T const& s) const noexcept {
return std::hash<int>{}(std::ref(s).get());
}
};
template <typename T>
struct MyEquals {
constexpr bool operator()(const T &lhs, const T &rhs) const {
return lhs == rhs;
}
};
struct MyHash_A {
std::size_t operator()(A const& s) const noexcept {
return std::hash<int>{}(s.x);
}
};
struct MyEquals_A {
constexpr bool operator()(const A &lhs, const A &rhs) const {
return lhs.x == rhs.x;
}
};
int main() {
std::unordered_map<A, int, MyHash_A, MyEquals_A> k_s;
std::unordered_map<std::reference_wrapper<int>, std::reference_wrapper<const A>, MyHash<std::reference_wrapper<int>>, MyEquals<std::reference_wrapper<int>>> k_i;
{
A a(5);
std::cout << "1----\n";
k_s[a] = 12;
std::cout << "2----\n";
}
std::cout << "3----\n";
print_um<>(k_s);
std::cout << "4----\n";
A a(5);
std::cout << "5----\n";
auto it = k_s.find(a);
std::cout << "6----\n";
k_i.emplace((*it).second, (*it).first);
// // k_i[(*it).second] = ref_name;
std::cout << "7----\n";
print_um<>(k_s);
std::cout << "8----\n";
print_um<>(k_i);
std::cout << "9----\n";
int x = 12;
int &ref = x;
auto is_there = k_i.find(ref);
if (is_there != k_i.end()) {
std::cout << "elem: " << (*is_there).second.get() << "\n";
} else {
std::cout << "why? :(\n";
}
std::cout << "10---\n";
return 0;
}
As to why I create this code, I was thinking to be able to access some data by value or by key interchangeably (is there some better data structure? ). Like an username and a token, sometimes I have one, other times I have the other and using references I ensure that I don't waste space. Ofc, if one value has to change, I would invalidate the bucket position in the unordered_map because of the key, but I would treat that problem at a later date. Another motive is to learn some more C++ and test its limits (or mine).
From UnorderedAssociativeContainer requirements:
For std::unordered_map and std::unordered_multimap the value type is std::pair<const Key, T>.
In your code k_s is unordered_map<A, int>, so the value type is pair<const A, int>. In here:
auto it = k_s.find(a);
you get a "pointer" to such pair, and type of (*it).first is const A.
Your k_i is unordered_map<..., ref<A>> and when you do insert here:
k_i.emplace(..., (*it).first);
you essentially attempt to initialize ref<A> with const A, which obviously cannot work.
When you change k_i type to unordered_map<..., ref<const A>>, then you initialize ref<const A> with const A, which is fine.
Many have mentioned in the comment that Key type must be const. However the OP seems to wonder why the value type in k_i also need to be a const.
The reason for that is because you are referencing the key from k_s, which would be const A, and you can not reference it with a non-const reference.
To properly declare k_s, you might want to do something like:
std::unordered_map<
std::reference_wrapper<decltype(k_s)::value_type::second_type>,
std::reference_wrapper<decltype(k_s)::value_type::first_type>,
yourHash, yourComp
> k_s;
One alternative solution for you is to use another container that actually supports bidirectional lookup, such as Boost.Bimap.

Is there std::variant and std::visit way to replace old legacy dispatch for messy code?

There is an old legacy code which can not be modified. If simplified it looks like this:
enum class Type {A, B, C};
struct Object {Type type;};
Object* objs[N];
int count = 0;
#define addobj(ptr) objs[count++] = (Object*)ptr
struct A {
Type type;
int prop1;
A(int v) : type(Type::A), prop1(v) {addobj(this);}
};
struct B {
Type type;
int prop1;
B(int v) : type(Type::B), prop1(v) {addobj(this);}
};
struct C {
Type type;
int prop1;
C(int v) : type(Type::C), prop1(v) {addobj(this);}
};
A* getA(int id) {return (A*)objs[id];}
B* getB(int id) {return (B*)objs[id];}
C* getC(int id) {return (C*)objs[id];}
In addition, there is "polymorphic" property access, which is allowed to change:
int& prop1ref(int id) {
switch (objs[id]->type) {
case Type::A: return getA(id)->prop1;
case Type::B: return getB(id)->prop1;
}
return getC(id)->prop1;
}
void test() {
A a(1); B b(2); C c(3);
for (int id=0; id<count; id++)
prop1ref(id);
}
Is there a way to replace only property access code in prop1ref with e.g. std::variant and std::visit?
Please note, prop1 name and type do match between classes, but locations (offsets) do not. Type field offset is guaranteed so cast can always be made. Also, new code should allow accessing double prop2, string prop3 etc in A, B, C classes without using macros.
Possibly something like this:
#include <variant>
#include <type_traits>
#include <iostream>
// ...
std::variant<A*, B*, C*> fetch_from_id(int const id) {
switch (objs[id]->type) {
case Type::A: return getA(id);
case Type::B: return getB(id);
default: return getC(id);
}
}
void test() {
A a(1); B b(2); C c(3);
for (int id = 0; id < count; id++)
std::visit([] (auto&& v) {
using type = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<type, A*>) {
// For A
std::cout << v->prop1 << std::endl;
}
if constexpr (std::is_same_v<type, B*>) {
// For B
std::cout << v->prop1 << std::endl;
}
if constexpr (std::is_same_v<type, C*>) {
// For C
std::cout << v->prop1 << std::endl;
}
}, fetch_from_id(id));
}
If you want to see for yourself if it works:
Demo
The below will allow you to extract other properties in a very easy way using lambdas:
#include <variant>
#include <string>
#include <iostream>
enum class Type {A, B, C};
struct Object {Type type;};
Object* objs[3];
int count = 0;
#define addobj(ptr) objs[count++] = (Object*)ptr
struct A {
Type type;
int prop1;
A(int v) : type(Type::A), prop1(v) {addobj(this);}
};
struct B {
Type type;
int prop1;
B(int v) : type(Type::B), prop1(v) {addobj(this);}
};
struct C {
Type type;
int prop1;
C(int v) : type(Type::C), prop1(v) {addobj(this);}
};
A* getA(int id) {return (A*)objs[id];}
B* getB(int id) {return (B*)objs[id];}
C* getC(int id) {return (C*)objs[id];}
using var_t = std::variant<A*,B*,C*>;
var_t fetch_from_id(int const id) {
switch (objs[id]->type) {
case Type::A: return getA(id);
case Type::B: return getB(id);
default: return getC(id);
}
}
// straightforward lambdas for extracting any property from your objects
auto get_prop1 = [](auto&& t){ return t->prop1;};
auto get_prop2 = [](auto&& t){ return t->prop2;};
auto get_prop3 = [](auto&& t){ return t->prop3;};
int main()
{
A a(1); B b(2); C c(3);
// print prop1
for (int id=0; id<3; id++) std::cout << std::visit(get_prop1, fetch_from_id(id)) << " ";
std::cout << std::endl;
// print prop2
// for (int id=0; id<3; id++) std::cout << std::visit(get_prop2, fetch_from_id(id)) << " ";
// std::cout << std::endl;
// print prop3
// for (int id=0; id<3; id++) std::cout << std::visit(get_prop3, fetch_from_id(id)) << " ";
// std::cout << std::endl;
}
Live code here

Can boost variants safely be used with pointers to forward declared classes?

Can boost variant safely accept pointers to classes that are forward declared without any unintended implications such as using them with visitors?
class A;
class B;
typedef boost::variant<A*, B*> Variant;
class A {
public:
A() {}
};
class B {
public:
B() {}
};
I'd suggest using the builtin recursive element support for this exact purpose. It makes the (de)allocation(s) automatic and exception safe.
Here's a complete demo where B actually recursively contains a vector<Variant> (which is 90% of the use-cases for forward-declared element types):
Live On Coliru
#include <boost/variant.hpp>
#include <iostream>
#include <iomanip>
struct A;
struct B;
typedef boost::variant<A, B> Variant;
struct A {
int solution = 42;
};
struct B {
std::string answer = "Thanks for all the fish!";
std::vector<Variant> other { A{1}, A{2}, B{"Three", {}}, A{4} };
};
struct Visitor {
std::string indent = " - ";
void operator()(Variant const& v) const {
boost::apply_visitor(Visitor{" " + indent}, v);
}
void operator()(A const& a) const { std::cout << indent << a.solution << "\n"; };
void operator()(B const& b) const {
std::cout << indent << std::quoted(b.answer) << "\n";
for (auto& v : b.other) {
operator()(v);
}
};
};
int main()
{
Variant v;
v = A{};
boost::apply_visitor(Visitor{}, v);
v = B{};
boost::apply_visitor(Visitor{}, v);
}
Prints
- 42
- "Thanks for all the fish!"
- 1
- 2
- "Three"
- 4

Virtual overloading of the comparison operator

Suppose we have the following snippet:
class A
{
public:
virtual bool operator< (const A &rhs) const;
};
class B: public A;
class C: public A;
I want the comparison to depend on the real types of both the left and right hand side, for example:
x < y == true if type(x) == B and type(y) == C
x < y == false if type(x) == C and type(y) == B
The situation could be more complex, with much more derived classes than two. Of course, operator< has to be a virtual function. Is there an elegant way to write this?
///
/// goal: provide partial ordering of objects derived from A on the basis
/// only of class type.
/// constraint: ordering specified by us
///
#include <vector>
#include <typeindex>
#include <algorithm>
#include <iostream>
class A
{
public:
virtual bool operator< (const A &rhs) const = 0;
static const std::vector<std::type_index>& ordering();
};
template<class T> struct impl_A : public A
{
bool operator< (const A &rhs) const override
{
auto& o = ordering();
auto first = std::begin(o);
auto last = std::end(o);
auto il = std::find(first, last, typeid(T));
auto ir = std::find(first, last, typeid(rhs));
return il < ir;
}
};
class B: public impl_A<B> {};
class C: public impl_A<C> {};
const std::vector<std::type_index>& A::ordering()
{
// specify fording of types explicitly
static const std::vector<std::type_index> _ordering { typeid(B), typeid(C) };
return _ordering;
}
void test(const A& l, const A& r)
{
if (l < r) {
std::cout << typeid(l).name() << " is less than " << typeid(r).name() << std::endl;
}
else {
std::cout << typeid(l).name() << " is not less than " << typeid(r).name() << std::endl;
}
}
int main()
{
test(B(), C());
test(B(), B());
test(C(), B());
test(C(), C());
}
example output (clang):
1B is less than 1C
1B is not less than 1B
1C is not less than 1B
1C is not less than 1C
Fine! But (I was not precise enough in my question), when x and y share the same type (for example B), the result of x < y is given by a specific function const operator< (B &rhs) const in class ̀B. It is not necessarily false`.
OK, so we are revising requirements. This is a normal dialogue between users (who rarely realise the level of detail required in specifications) and the developers (who do!)
So this time we will say that any two dissimilar derived classes will have a consistent partial ordering (i.e. they will never compare equal and one will always compare less than the other) but we'll let the standard library decide which one comes first.
However, when the two classes being compared are of the same type, we would like to actually compare their values to determine ordering (and equivalence).
It would go something like this:
#include <vector>
#include <typeinfo>
#include <algorithm>
#include <iostream>
#include <tuple>
#include <iomanip>
class A
{
public:
virtual bool operator< (const A &rhs) const = 0;
std::ostream& print(std::ostream& os) const {
handle_print(os);
return os;
}
private:
virtual void handle_print(std::ostream&) const = 0;
};
std::ostream& operator<<(std::ostream& os, const A& a) {
return a.print(os);
}
template<class T> struct impl_A : public A
{
bool operator< (const A &rhs) const override
{
auto& rhs_info = typeid(rhs);
auto& lhs_info = typeid(T);
if (rhs_info == lhs_info) {
// same type, so do comparison
return static_cast<const T&>(*this).ordering_tuple() < static_cast<const T&>(rhs).ordering_tuple();
}
else {
return lhs_info.before(rhs_info);
}
}
};
class B: public impl_A<B> {
public:
B(int v) : _value(v) {}
auto ordering_tuple() const {
return std::tie(_value);
}
private:
void handle_print(std::ostream& os) const override {
os << _value;
}
int _value;
};
class C: public impl_A<C> {
public:
C(std::string v) : _value(std::move(v)) {}
auto ordering_tuple() const {
return std::tie(_value);
}
private:
void handle_print(std::ostream& os) const override {
os << std::quoted(_value);
}
std::string _value;
};
// now we need to write some compare functions
void test(const A& l, const A& r)
{
if (l < r) {
std::cout << l << " is less than " << r << std::endl;
}
else {
std::cout << l << " is not less than " << r << std::endl;
}
}
int main()
{
test(B(1), C("hello"));
test(B(0), B(1));
test(B(1), B(0));
test(B(0), B(0));
test(C("hello"), B(1));
test(C("goodbye"), C("hello"));
test(C("goodbye"), C("goodbye"));
test(C("hello"), C("goodbye"));
}
example results:
1 is less than "hello"
0 is less than 1
1 is not less than 0
0 is not less than 0
"hello" is not less than 1
"goodbye" is less than "hello"
"goodbye" is not less than "goodbye"
"hello" is not less than "goodbye"
The only solution I see is to not have the operator< function as a virtual member function, but as a set of overloaded non-member functions: One "default" function which takes two references to A as arguments, and then one overload each for the special cases.

Overloading an operator through friend function and returning a type different from the rhs parameters

I am attaching the code here and explaining the problem below:
Here is the class Bitop:
#ifndef _Bitop_H
#define _Bitop_H
# include <iostream>
double num2fxp(double v, int bits=9, int intbits=5){
return -0.5;
}
template<int bits = 8, int intbits = 6>
class Bitop
{
template<int rhsbits, int rhsintbits> friend class Bitop;
private:
double value; // data value
public:
Bitop(const double& v=0):
value(num2fxp(v, bits, intbits))
{}
template<int rhsbits, int rhsintbits>
const Bitop<bits, intbits>& operator = (const Bitop<rhsbits, rhsintbits>& v){
value = num2fxp(v.value, bits, intbits);
return *this;
}
template<int rhsbits, int rhsintbits>
Bitop<bits, intbits>& operator += (const Bitop<rhsbits, rhsintbits>& v) {
value = num2fxp(value+v.value, bits, intbits);
return *this;
}
template<int lhsbits, int lhsintbits, int rhsbits, int rhsintbits>
friend Bitop<lhsintbits+rhsintbits+2, lhsintbits+rhsintbits+1> operator + (const Bitop<lhsbits, lhsintbits>& x, const Bitop<rhsbits, rhsintbits>& y){
return Bitop<lhsintbits+rhsintbits+2, lhsintbits+rhsintbits+1> (num2fxp(x.value+y.value));
}
friend std::ostream& operator<< (std::ostream & out, const Bitop& y){return out << y.value ;}
void Print(){
std::cout << value<< "<"
<< bits << ","
<< intbits << ">";
}
};
#endif
And the Test function:
# include <iostream>
# include "Bitop.H"
using namespace std;
int main (int argc, char** argv) {
Bitop<4,1> a = 0.8;
Bitop<5,2> b(3.57);
Bitop<7,3> c;
c = b;
cout << "See all attributes of c \n";
c.Print();cout << "\n";
c = 7.86;
cout << "reassign c to a new value\n";
c.Print();cout << "\n";
cout << "set b = c \n";
b = c;
b.Print();cout<<"\n";
cout << "set b+=a \n";
b += a;
b.Print();cout<<"\n";
cout << "set b=c+a \n";
b = c+a;
b.Print();cout<<"\n";
return 0;
}
I have a templated class Bitop. I want to overload "+" to add 2 objects with different template parameters and return a third object with parameters different from the rhs and lhs objects, i.e. I want to do the following:
Bitop<5,3> + Bitop<4,2> should return Bitop<10,6>. I declared Bitop to be a friend class of itself so I can access the private members of rhs and lhs objects. But I am getting compilation error (due to redefinition) regardless of whether I call the "+" function.
I am not clear about what I am doing wrong here. Any help is appreciated.
Please note that I left a couple of functions and function calls in the code to ensure that other overloads such as = and += work correctly.
Here's a simplified example that shows the same problem:
template<int i>
struct X {
template<int a, int b>
friend void foo(X<a>, X<b>) { }
};
int main()
{
X<1> x1;
X<4> x2; // error: redefinition of foo
}
Every time a new specialization of X gets instantiated, the definition of foo is inserted in the scope sorounding the template class X. I hope it's clear where the error is coming from.
Things would be different if the declaration depended on template parameter of the class, like:
template<int i>
struct X {
template<int a, int b>
friend void foo(X<a+i>, X<b+i>) { } // different definiton
// for each specialization of X
};
The solution is to define the friend function outside of class:
template<int i>
struct X {
template<int a, int b>
friend void foo(X<a>, X<b>);
};
template<int a, int b>
void foo(X<a>, X<b>) { }
int main()
{
X<1> x1;
X<4> x2;
}
You don't really need to define operator+ as friend:
template<int I>
class A
{
double X;
template<int> friend class A;
public:
A(A const&) = default;
A(double x)
: X(x) {}
template<int J>
A(A<J> const&a)
: X(a.X) {}
template<int J>
A<I+J> operator+ (A<J> const&a)
{ return A<I+J>(X+a.X); }
};
int main()
{
A<0> a0(3);
A<1> a1(4);
auto ax = a0+a1;
}
Moreover, the result returned by operator+(a,b) should really be identical to that obtained by operator=(a) followed by operator+=(b).