Polymorphism virtual abstract operator overriding.

I am trying to implement something similar to sort command in unix using polymorphism. I want to store items in memory in their "natural" type, meaning that numbers are represented as ints, string (and everything what cannot be determined) as std::string and so on.
I have abstract class AbstractVal and derived classes IntVal and StringVal (DoubleVal, ComplexVal, ...). I want to implement operators ==, < and > so I can use functor to sort them. I have following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class AbstractVal {
public:
	virtual std::string to_string() = 0;
	virtual std::unique_ptr<AbstractVal> clone() = 0;
	virtual bool operator==(const AbstractVal& val) = 0;
	virtual bool operator<(const AbstractVal& val) = 0;
	virtual bool operator>(const AbstractVal& val) = 0;
};

class IntVal : public AbstractVal {
public:
	IntVal(std::string x) : x_(std::stoi(x)) {};
	virtual std::string to_string() override { return std::to_string(x_); }
	virtual std::unique_ptr<AbstractVal> clone() override { return std::make_unique<IntVal>(*this); }
	virtual bool operator==(const AbstractVal& val) override { return x_ == ((IntVal)val).x_; }
	virtual bool operator>(const AbstractVal& val) override { return x_ > ((IntVal)val).x_; }
	virtual bool operator<(const AbstractVal& val) override { return x_ < ((IntVal)val).x_; }
private:
	int x_;
};

class StringVal : public AbstractVal {
public:
	StringVal(std::string x) : x_(x) {};
	virtual std::string to_string() override { return x_; }
	virtual std::unique_ptr<AbstractVal> clone() override { return std::make_unique<StringVal>(*this); }
	virtual bool operator==(const AbstractVal& val) override { return x_ == ((StringVal)val).x_; }
	virtual bool operator>(const AbstractVal& val) override { return x_ > ((StringVal)val).x_; }
	virtual bool operator<(const AbstractVal& val) override { return x_ < ((StringVal)val).x_; }
private:
	std::string x_;
};


But it doesn't work as I'd expect it to. It doesn't even compile because I don't know how to correctly implement converting constructors. How am I supposed to override those operators in derived classes?
Last edited on
What does it even mean to compare two objects of arbitrary type?
Is your goal simply to convert everything to strings and then compare the strings lexicographically?
No, my goal is to compare them based on its type. For example if I have two IntVal objects, I want to compare them as ints.

If you are confused with to_string() methods, they're used to print output (I'm used to override ToString() from C#...)

The problem is I have two objects std::unique_ptr<AbstractVal> and I want to compare them, without knowing which derived type it is. The only thing I know they both have the same (derived) type.

Let's say I have following code
1
2
3
4
std::unique_ptr<AbstractVal> first = std::make_unique<IntVal>(5);
std::unique_ptr<AbstractVal> second = std::make_unique<IntVal>(10);

bool result = first.get() > second.get();


Using the last command, I want to set result to false (based on the comparision).
Last edited on
mbozzi wrote:
What does it even mean to compare two objects of arbitrary type?
Speedding wrote:
No, my goal is to compare them based on its type. For example if I have two IntVal objects, I want to compare them as ints.


You answer isn't an answer. What does it mean to compare two objects of arbitrary type? Nevermind that the objects may be of the same type. Is "Purple" greater than 3?

The (Unix) sort you referred to in the OP operates on strings/text, not objects of arbitrary type. If you're trying to simulate that, you're going about it in a strange way.
Maybe you want to sort lexicographically based somehow on derived type as the most-significant discriminator. Everything that can only be interpreted as an string goes first, for example.

If you are confused with to_string() methods, they're used to print output

You should consider defining std::ostream& operator<<(std::ostream&, IntVal) so you may print IntVal objects in the idiomatic manner some_stream << my_intval << '\n';.

Using the last command, I want to set result to false (based on the comparision).

You surely mean
 
bool /* const? */ result = *first > *second;

Rather than comparing the pointers, (which, strictly speaking, exhibits unspecified behavior.)
Last edited on
They will be surely of the same type, because I specify types of columns on the input. If the input cannot be parsed (for example if I mark some column as numeric and stoi() function throws an exception), the program is terminated. I didn't say I want to implement unix sort, I said I want to implement something similar to it. Sorry for misunderstanding.

The structure I have looks like this. I have class Row which implements vector<unique_ptr<AbstractVal>>. Based on user input, the derived classes are chosen. So if I have row looking like this one

some text#12345#1654

and user's input looks like this

S1 N2

where S1 means first column will be string type, N2 second column will be numeric (int) type

means that "some text" will be parsed as string (unique_ptr<StringVal>), "12345" will be parsed as int (unique_ptr<IntVal>) -> if couldn't be parsed, throws an exception and "1654" will be parsed as string, because the type wasn't declared by user.

Then I have vector<Row> which represents whole table. So back to the user's input, if I have S1 N2, I want to sort at first by first column and if some elements nf the first column are the same (have the same value) then I want to sort by second column. I think the best way doing that is using a functor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SortingAlg {
	public:
		SortingAlg(const std::vector<std::pair<std::size_t, std::string>>& colnum_type) : colnum_type_(colnum_type) {}
		bool operator()(Row& first, Row& second) { return compare_to_(first, second, 0); }
	private:
		bool compare_to_(Row& first, Row& second, std::size_t priority);
		std::vector<std::pair<std::size_t, std::string>> colnum_type_;
	};


bool Polysort::SortingAlg::compare_to_(Row & first, Row & second, std::size_t priority) {
	if (priority == colnum_type_.size())
		return true;
	auto colnum = colnum_type_[priority].first - 1;
	if (first[colnum] == second[colnum])
		return compare_to_(first, second, priority + 1);
	else
		return first[colnum] > second[colnum];
}

In the constructor I pass reference to vector containing user's input, meaning that example with S1, N2...


This is the reason I want to implement those operators.

I can send you the whole code if it helps you to orient in my problem. It has about 400 lines and the parsing and printing output already work as I wish to (although I have to improve the code to look "nicer").

Thanks for hint with overloading << operator, didn't know about it and I will keep it on mind.
But then I have the same problem as with ==, < and >. In AbstractVal definition I have this
friend virtual std::ostream& operator<<(std::ostream& os, AbstractVal& val) = 0;
and while trying to give it its implementation in derived classes, I have to override it and I need to convert AbstractVal& val to (for example) IntVal& val somehow, because value of the given object is marked as private in those derived class (and isn't defined in abstract class at all).
Last edited on
Solved using dynamic_cast.

 
virtual bool operator==(const AbstractVal& val) const override { return x_ == dynamic_cast<const StringVal&>(val).x_; }
I said I want to implement something similar to it

In order to get decent help, you need to be precise about your problem.

I have S1 N2, I want to sort at first by first column and if some elements nf the first column are the same (have the same value) then I want to sort by second column. I think the best way doing that is using a functor.
std::lexicographical_compare:
http://en.cppreference.com/w/cpp/algorithm/lexicographical_compare
Last edited on
Here is an alternative approach using pure value semantics and compile-time polymorphism.
This may result in cleaner code that is also measurably more efficient.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <iostream>
#include <string>
#include <variant>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <utility>
#include <sstream>
#include <functional>
#include <random>
#include <cassert>

namespace my
{
    ////////////////////   item ///////////////////////////////

    // std::variant http://en.cppreference.com/w/cpp/utility/variant (C++17)
    // ( use boost::variant http://www.boost.org/doc/libs/1_66_0/doc/html/variant.html
    //   if the implementation does not support C++17 )
    using item = std::variant< int, std::string, double /* , ... */ > ;

    enum type_t : std::size_t { INT = 0, STR = 1, DBL = 2, /* ... */ NOTHING = std::variant_npos };

    type_t type( const item& it ) { return type_t( it.index() ) ; }

    item make_item( const std::string& str, type_t type )
    {
        if( type == INT )
        {
            try { return std::stoi(str) ; }
            catch( const std::exception& ) { return 0 ; } // perl-like
        }
        else if( type == STR ) return str ;
        else if( type == DBL )
        {
            try { return std::stod(str) ; }
            catch( const std::exception& ) { return 0.0 ; } // perl-like
        }
        // else if ...
        // etc.

        return {} ; // no match: return value initialised item (int==0 in our case)
    }

    std::ostream& operator<< ( std::ostream& stm, const item& it )
    {
        // note: we could use a visitor here http://en.cppreference.com/w/cpp/utility/variant/visit
        if( type(it) == INT ) return stm << std::get<int>(it) ;
        else if( type(it) == DBL ) return stm << std::get<double>(it) ;
        else if( type(it) == STR ) return stm << std::quoted( std::get<std::string>(it) ) ;
        // else if ...
        else return stm << "<uninitalised>" ;
    }

    // note: items are compared using the comparison operators for variants
    // http://en.cppreference.com/w/cpp/utility/variant/operator_cmp
    // ie. items of the same type are compared using the comparison operators of the type
    //     items of different types are compared by comparing their indices INT < STR etc.
    //     comparing uninitialised items returns an unordered result even when
    //           comparing with itself. (behaves like NaN for floating point values.)


    ////////////////////// row //////////////////////////////////
    using row = std::vector<item> ;

    bool operator< ( const row& a, const row& b )
    { return std::lexicographical_compare( std::begin(a), std::end(a), std::begin(b), std::end(b) ) ;}

    bool operator== ( const row& a, const row& b )
    { return std::equal( std::begin(a), std::end(a), std::begin(b), std::end(b) ) ; }

    using namespace std::rel_ops ; // for implicitly generating the other relational operators for row

    row make_row( const std::string& line, const std::vector<type_t>& types, char delim )
    {
        row result ;

        std::istringstream stm( line + delim ) ;
        for( type_t ty : types )
        {
            std::string tok ;
            std::getline( stm, tok, delim ) ;
            result.push_back( make_item( tok, ty ) ) ;
        }

        return result ;
    }

    std::ostream& operator<< ( std::ostream& stm, const row& r )
    {
        for( const item& it : r ) stm << it << " | " ;
        return stm ;
    }

    ///////////////////////// table ///////////////////////////////
    using table = std::vector<row> ;

    table make_table( std::istream& stm, const std::vector<type_t>& types, char delim = '#' )
    {
        table result ;

        std::string line ;
        while( std::getline( stm, line ) ) result.push_back( make_row( line, types, delim ) ) ;

        return result ;
    }

    std::ostream& operator<< ( std::ostream& stm, const table& t )
    {
        for( const row& r : t ) stm << r << '\n' ;
        return stm ;
    }
}

///////////////////////  minimal test driver /////////////////////
int main()
{
    std::istringstream file
    (
        "some text#12345#1654#-72345#1.23\n"
        "aaaa bbbb#99999#zzzz#444444#5.78\n"
        "some text#23456#$$$$#128607#0.99\n"
        "some text#23456#$$$$#128600#2.34\n"
        "aaaa bbbb#99999#zzzz#222222#0.66.23\n"
        "aaaa bbbb#99999#zzzz#444444#1.45\n"
        "some text#12345#165 #000123456#4.56\n"
        "aaaa bbbb#99999#zzzx#999999#9.99\n"
    );

    using namespace my ; // note: this also brings in std::rel_ops

    auto data = make_table( file, { STR, INT, STR, INT, DBL } ) ;
    std::cout << data << '\n' ;

    std::sort( std::begin(data), std::end(data) ) ;
    std::cout << data << '\n' ;

    std::shuffle( std::begin(data), std::end(data), std::mt19937( std::random_device{}() ) ) ;
    std::cout << data << '\n' ;

    std::sort( std::begin(data), std::end(data), std::greater<>() ) ; // operator >
    std::cout << data << '\n' ;

    assert( data[0] >= data[1] && data[2] == data[2] && data[5] <= data[2] ) ; // operators >=, ==, <=
    std::cout << "\nok\n" ;
}

http://coliru.stacked-crooked.com/a/ffd455f9e361535e
Topic archived. No new replies allowed.