Template Function - Vector of Classes w/ Different Constructors

Hi, first post. I've recently been introduced to templates and am hoping to implement them into a project I am working on; however, I've run into a few problems with design. The program I am writing uses an `Sqlite3` database, for which I am creating a wrapper to complete and store queries in an `std::vector`. I have a number of different classes to handle each table in the database, and created the function below to handle any class type.

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
template<class T>
void query(sqlite3* db, const char* query, vector<T>& vec) {

	sqlite3_stmt *statement;

	if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) {
		int cols = sqlite3_column_count(statement);
		vector<int> index;
		for (int col = 0; col < cols; ++col) {
			index.push_back(col);
		}
		int result = 0;
		while (true) {
			result = sqlite3_step(statement);
			if (result == SQLITE_ROW) {
				vec.push_back(T(
					sqlite3_column_int(statement, index[0]),
					(char*)sqlite3_column_text(statement, index[1]),
					(int)sqlite3_column_int(statement, index[2]),
					(int)sqlite3_column_int(statement, index[3]),
					(char*)sqlite3_column_text(statement, index[4]),
					(int)sqlite3_column_int(statement, index[5])));
			}
			else {
				break;
			}
		}
		sqlite3_finalize(statement);
	}

	string error = sqlite3_errmsg(db);
	if (error != "not an error") cout << query << " " << error << endl;

	sqlite3_close(db);
}


However, while the above works perfectly well I am having difficulty choosing a manner in which to apply it (or something similar) to all classes. As I said, each class maps to a particular table in the database, and its members to the values in the columns. In order to decrease the size of the database I have elected to store categorical information as `int`s and more specific (non-recurring) information as `string`s. For example the above is the implementation that corresponds to the following class `Car`.
The header:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Car {
private:
	int _carID;
	string _carModelName, _carEngineManufacturer;
	int _carModelYear, _carColor, _carEngineDisplacement;
public:
	Car(int carID, string carModelName, int carModelYear, int carColor, string carEngineManufacturer, int carEngineDisplacement) {
		_carID = carID;
		_carModelName = carModelName;
		_carModelYear = carModelYear;
		_carColor = carColor;
		_carEngineManufacturer = carEngineManufacturer;
		_carEngineDisplacement = carEngineDisplacement;
	}
	int displayCarID() const;
	string displayCarModelName() const;
	string displayCarModelYear() const;
	string displayCarColor() const; // if _carColor == 1 return "RED"; _carColor == 2 return "BLUE"; ect...
	string displayCarEngineManufacturer() const;
	string displayEngineDisplacement() const;
};


The `std::vector` containing the data for each class is then used to display it (again specific to the above) as such:

1
2
3
4
5
6
7
8
9
10
11
12
template<class T>
void displayList(const vector<T> &ex) {
	for (vector<T>::const_iterator it = ex.begin(); it != ex.end(); ++it) {
		cout << it->displayCarID() << endl;
		cout << it->displayCarModelName() << endl; // model name
		cout << it->displayCarModelYear() << endl; // year
		cout << it->displayCarColor() << endl; // color
		cout << it->displayCarEngineManufacturer() << endl; // engine manufacture
		cout << it->displayEngineDisplacement() << endl; // engine displacement
		cout << "\n" << endl;
	}
}


On to the question. Is it possible to use a template for the above `query` function in order to avoid replicating it for each individual class? If not, what is the preferred design (or your own personal advice) for simplifying a problem like this?

Restrictions:

1) The `sqlite3` C++ interface returns queries in `C` style datatypes. For example in the first code snippet in order to get the string `carEngineManufacturer` required for the constructor of `Car` a C style cast is required as `(char*)sqlite3_column_text(statement, index[1])`.

2) Since the classes vary, another constructor may require a different order or number or arguments. IE creating a vector of planes. `Plane(int planeName, string planeModelName, int planeModelYear, string countryOfOrigin, int planeType);`

Considerations:

I considered creating a `base` class to derive from as all classes will share at least an `ID` and `Name` member, however it seems as though that would not be altogether very beneficial. Moreover, as none of the classes have, or are aware of a `vector` class member adding a specific function to their implementations would give a `non-static` or `storage` error.

I also thought about specializing the template for each data class type, which at the moment is the best solution I am aware of. Albeit, that would require the majority of the same code to be copied more than 10 times changing only that is within the `if (result == SQLITE_ROW) { }` brackets, and was curious if there is a better/simpler solution I am overlooking. I would greatly appreciate any other insight or advice, thanks in advance.

*Note – If it helps, the `int cols = sqlite3_column_count(statement);` line the template function returns the number of columns for the specific query which could possibly be used to determine the class.
Last edited on
The answer is Polymorphism.

The idea is simple: Create an interface filled with methods which all your other classes should implement

Example of such a class:
1
2
3
4
5
6
7
8
9
10
11
class Table {
    protected:
        virtual void display(std::ostream& oss) const = 0;
    public:
        Table(sqlite3_stmt *statement) {}
        virtual ~Table() {}
        friend std::ostream& operator<< (std::ostream& oss, const Table& data) {
            data.display(oss);
            return oss;
        }
};


Now what you can do is that for each table you have in your db, create the tables as normal, but have them implement this interface as well. In that way, you have no code duplication and every class will have the same methods required for working with it.
Continuing with the Car class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Car : public Table {
  private:
    int _carID;
    std::string _carModelName, _carEngineManufacturer;
    int _carModelYear, _carColor, _carEngineDisplacement;
  
  protected:
    void display(std::ostream& oss) const {
        // oss << this->_carID << '\n';
        // oss << this->_carModelName << '\n';
        // .....
    }

  public:
    Car (sqlite3_stmt *statement) : Table(statement) {
        // Do all sql stuff here
    }
};


Finally your query method should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void query(sqlite3* db, const char* query, std::vector<std::shared_ptr<Table>> & vec) {
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) {
        for (int result = sqlite3_step(statement); result == SQLITE_ROW;
            result = sqlite3_step(statement)) {
            // In here you have to somehow know the object you are dealing with. For this
            // example, let's assume Car object as before
            vec.push_back(std::make_shared<Car>(statement));
        }
        sqlite3_finalize(statement);
    }

    std::string error = sqlite3_errmsg(db);
    if (error != "not an error") {
        std::cout << query << " " << error << std::endl;
    }

    sqlite3_close(db);
}


And display method should look like this:

1
2
3
4
5
6
void displayList(const std::vector<std::shared_ptr<Table>> &ex) {
    for (std::vector<std::shared_ptr<Table>>::const_iterator it = ex.begin();
        it != ex.end(); ++it) {
        std::cout << *(it->get()) << std::endl;
    }
}


Note all this is untested but should any compiler errors arise they should be easy enough fixes
Last edited on
However, while the above works perfectly well I am having difficulty choosing a manner in which to apply it (or something similar) to all classes.

So, basically, you want to template the boilerplate and be able to plug in the type-specific code (which appears to be just the creation of an object, here.)

A simplistic solution:
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
class Car {
private:
    int _carID;
    string _carModelName, _carEngineManufacturer;
    int _carModelYear, _carColor, _carEngineDisplacement;

    static Car extract(sqlite3_stmt* st)
    {
        return Car(sqlite3_column_int(st, 0),
            (char*) sqlite3_column_text(st, 1),
            sqlite3_column_int(st, 2),
            sqlite3_column_int(st, 3),
            (char*) sqlite3_column_text(st, 4),
            sqlite3_column_int(st, 5));
    }

public:
    Car(int carID, string carModelName, int carModelYear, int carColor, string carEngineManufacturer, int carEngineDisplacement) {
        _carID = carID;
        _carModelName = carModelName;
        _carModelYear = carModelYear;
        _carColor = carColor;
        _carEngineManufacturer = carEngineManufacturer;
        _carEngineDisplacement = carEngineDisplacement;
    }
    int displayCarID() const;
    string displayCarModelName() const;
    string displayCarModelYear() const;
    string displayCarColor() const;
    string displayCarEngineManufacturer() const;
    string displayEngineDisplacement() const;

    static std::function<Car(sqlite3_stmt*)> getExtractionStrategy() { return extract; }
};


template<class T>
std::vector<T> query(sqlite3* db, const char* query) 
{
    sqlite3_stmt *statement;

    std::vector<T> result;

    if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) 
    {
        std::vector<T> result;
        auto extract = T::getExtractionStrategy();
        while (SQLITE_ROW == sqlite3_step(statement))
            result.emplace_back(extract(statement));

        sqlite3_finalize(statement);
    }

    string error = sqlite3_errmsg(db);
    if (error != "not an error") cout << query << " " << error << endl;

    sqlite3_close(db);
    return result;
}


Where client types are required to have a static member GetExtractionStrategy that does the right thing.

1
2
    // A call would look like so:
    auto container = query<Car>(db, query_str); 


Caveat: Untested code.
1)
From the responses I have developed the following two functions:

1
2
3
4
5
6
template<class T>
void showQList(const vector<std::shared_ptr<T>> &vec) {
	for (vector<std::shared_ptr<T>>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
		cout << *(it->get()) << endl;
	}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<class T>
void query(sqlite3* db, const char* query, vector<std::shared_ptr<T>> &vec) {
	sqlite3_stmt *statement;
	if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) {
		for (int result = sqlite3_step(statement); result == SQLITE_ROW;
                result = sqlite3_step(statement)) {
			vec.push_back(std::make_shared<T>(statement));
		}
		sqlite3_finalize(statement);
	}
	string error = sqlite3_errmsg(db);
	if (error != "not an error") {
		cout << query << " " << error << endl;
	}
	sqlite3_close(db);
	showQList(vec);
}


And the following alternative to the query above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class T>
vector<std::shared_ptr<T>> queryAlt(sqlite3* db, const char* query) {
	sqlite3_stmt *statement;
	vector<std::shared_ptr<T>> vec;
	if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) {
		for (int result = sqlite3_step(statement); result == SQLITE_ROW;
                result = sqlite3_step(statement)) {
			vec.emplace_back(std::make_shared<T>(statement));
		}
		sqlite3_finalize(statement);
	}
	string error = sqlite3_errmsg(db);
	if (error != "not an error") {
		cout << query << " " << error << endl;
	}
	sqlite3_close(db);
	showQList(vec);
	return vec;
}


All three methods work well, and I am grateful for the responses that helped me arrive at forming them. I have been utilizing the first (`query`) because in my current implementation (see part 2 below) it resolves successfully. I would appreciate any other advice or criticism on the functions above.

*Note – I have based my classes on a suggestion from @Smac89 (Thank you!) and used an abstract base to form a heritable interface for display.

2)

One other quick question (I would otherwise open another topic but since the majority of the specific circumstance is here it seems appropriate). I am using two `switch statements` in order to build the text of database query (one for the operation, the following for the table to be used). I was curious about what manner (or otherwise) would be best suited to simplify the initialization of the `vector` with its members. Currently I am using the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class T>
vector<std::shared_ptr<T>> operationMenu(sqlite3* db) {
	int choice;
	do {
		/*show choices*/
		}
	} while (choice < 1 || choice > 5);
	string opType;
	vector<std::shared_ptr<T>> vec;
	switch (choice) {
	case 1: { opType = selectTable(); const char* sql = opType.c_str(); 
		query(db, sql, vec); break; }
	/*other cases*/
	}
}


A call to which looks as such:

 
auto vec = operationMenu<Car>(db);


I was wondering, and forgive me if this seems like an odd question, if it is possible to return the `vector` (and its type) directly instead of casting it like above. I.E. something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*template*/
/*vector*/ setList(sqlite3* db) {
	std::ostringstream select;
	select << "SELECT ";
	int option = userChoice();
	switch (option) {
	case 1: { select << "* FROM CARS"; string str = select.str();
		const char* sql = str.c_str(); vector<std::shared_ptr<Cars>> vec;
		query(db, sql, vec);
		return vec; break; }
	case 2: { select << "* FROM PLANES"; string str = select.str();
		const char* sql = str.c_str(); vector<std::shared_ptr<Planes>> vec;
		query(db, sql, vec); return vec; break; }
	case 3: { select << "* FROM BOATS"; string str = select.str();
		const char* sql = str.c_str(); vector<std::shared_ptr<Boats>> vec;
		query(db, sql, vec); return vec; break; }
	}
}


And a corresponding call:

 
auto vec = setList(db);


In any case, thanks to all for your input and how quick you were to offer it. I greatly appreciate it.
Last edited on
@cire Thanks for your response. I partially favored @Smac89’s answer because I required some way to display the input universally as well as that for some reason, perhaps my own fault, I could not get the `result vector` to populate. Nevertheless, your example helped a great deal; influence that is reflected in what I posted above. I was curious about your use of `emplace_back` and your choice for the related structuring.

I am a bit of novice, (especially when compared to you and the others here) I read previously that the when possible to prefer initialization to assignment. I also checked up on the documentation for `emplace_back` and it seems that it is preferred when construction (non-primitive types) takes place to add to the vector? I did some of my own bench marking with `emplace` and `push` back with some mixed results.

In your example the `extract` methods returns a `Car`, is that why `emplace` is preferred in this case? I have set the constructor for the classes as so:

1
2
3
4
5
6
7
8
Car(sqlite3_stmt *statement) : BTable(statement) {
	_carID = sqlite3_column_int(statement, 0);
	_carModelName = (char*)sqlite3_column_text(statement, 1);
	_carModelYear = sqlite3_column_int(statement, 2);
	_carColor = sqlite3_column_int(statement, 3);
	_carEngineManufacturer = (char*)sqlite3_column_text(statement, 4);
	_carEngineDisplacement = sqlite3_column_int(statement, 5);
}


Is emplace_back beneficial in this case?

Called to as: vec.emplace_back(std::make_shared<T>(statement));

Also, I was wondering if the way it is currently implemented (`query` not `queryAlt`), that is with a reference argument after the vector has been previously declared is more taxing/strenuous than initializing directly using auto as you did in your response? IE:

Initializing a vector as a return from a function like

auto vec = operationMenu<Car>(db);

V.S.

Declaring the vector and then populating it by passing it as a reference to a function

vector<std::shared_ptr<Car>> vec; ...query(db, sql, vec);

Again thanks for your help!
Hi,

Just some unrelated things:

Consider using a member initialisation list in your constructor, instead of assignment.

Rather than iterators from begin to end, try a range based for loop.

http://en.cppreference.com/w/cpp/language/initializer_list
http://en.cppreference.com/w/cpp/language/range-for


Hope this helps a little :+)
In your example the `extract` methods returns a `Car`, is that why `emplace` is preferred in this case? I have set the constructor for the classes as so:

There is no reason to prefer emplace_back for this particular use case. They've equivalent semantics for rvalue references. I just prefer to use emplace generally, since there are fewer "gotchas" with regards to generated temporaries in its use.


Also, I was wondering if the way it is currently implemented (`query` not `queryAlt`), that is with a reference argument after the vector has been previously declared is more taxing/strenuous than initializing directly using auto as you did in your response? IE:

What happens when you send a non-empty vector in? What should happen? Should it even be allowed? The alternate form handles that rather nicely, doesn't it? It also allows for type deduction on the return type (the use of auto, which may be good or bad here, depending on your views.)

You should reconsider the use of shared_ptr. Do you really need shared ownership?
cire is right in that you may not need shared_ptr. I am not very familiar with the way smart pointers work, but I don't think this is required in this instance. Maybe using a unique_ptr or some other type of smart pointer might work better. This part is up to you to decide, but the bottom line is that if pointers are stored in a vector, they have to be manually deleted when the vector goes out of scope; and smart pointers help with this.

There is another solution you can adopt which is as follows:

If you use the method proposed by @cire, i.e. this definition of query (with a little modification):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<class T>
std::vector<T> query(sqlite3* db, const char* query) {
    sqlite3_stmt *statement;

    std::vector<T> result;

    if (sqlite3_prepare_v2(db, query, -1, &statement, 0) == SQLITE_OK) {
        while (SQLITE_ROW == sqlite3_step(statement))
            result.emplace_back(statement);

        sqlite3_finalize(statement);
    }

    string error = sqlite3_errmsg(db);
    if (error != "not an error") cout << query << " " << error << endl;

    sqlite3_close(db);
    return result;
}


This will solve the problem you are facing.

Then in main, to collect them all you do this:
1
2
3
4
5
vector<Table> tables;

...
vector<Car> carQuery = query(...);
std::move(carQuery.begin(), carQuery.end(), std::back_inserter(tables));


Now your tables vector will contain all Tables

Your setList method can be modified like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::vector<Table> setList(sqlite3* db) {
    std::string select = "SELECT ";

    int option = userChoice();
    switch (option) {
	case 1:
            select += "* FROM CARS;";
            return query<Car>(db, select.c_str());
	case 2:
            select += "* FROM PLANES;";
            return query<Planes>(db, select.c_str());
	case 3: 
            select += "* FROM BOATS;";
            return query<Boats>(db, select.c_str());
	}

    return std::vector<Table>();
}


Last but not least, the display method should also be modified to accept vector of tables rather than vector of pointers
Last edited on
@TheIdeasMan thanks for the input. The initialization list was a valuable suggestion. I will look into the range loop.

@cire again that is a valid point, duly noted. Thank you.

@Smac89 I will hold onto the idea of collecting the tables in a single vector. Such could be useful at a later point, however my goal would be collect them separately in different vectors and/or an instance of an std::vector<Table> which contains the results from only one query at a time (to avoid later separation/sorting).

How would you call to setList ?

As vector<Table> desiredQuery = setList(db); ?

I believe I have some problem arising from inheritance. Presently I get the following error when trying to implement it:

no suitable user-defined conversion from "std::vector<Car, std::allocator<Car>>" to "std::vector<Table, std::allocator<Table>>" exists

My `Table` class is as verbatim from your first response, and all (derived) classes (with the exception of the overridden display function) are also structured as you did in your `Car` example.
Topic archived. No new replies allowed.