Looking for a pattern(s) for attaching a Table to an arbitrary C++ struct

Hi,

I have the following challenge for which I would truly appreciate some pointers. Basically I want to write a connector from a C++ struct to a DataTable without having to write repeating tedious code. I need a table that shows the contents of a std::vector<T> of T, displaying only selected columns in a default format for each column type.

Thus the "displayer" must receive T, and a variadic collection of columns and show the data in default formats. Later I would like to specify specific formatting per column...

I already have the Table part (it deals only with text columns so everything must be format to a string type). What I need is the architecture of how to join this Table with a struct: the displayer element.

All ideas welcomed and appreciated!


Regards,
Juan
not sure I fully get what you need, but what if you had a template class that held a type t, a "display me" boolean, and a 'format me' code?
then you could perhaps have
for each column
if display==true
print element using specified formatting

not sure of the best way to do the formatting ... maybe your own display function (<< overload) with the ones you need in a switch statement?
Yes but how do I handle the columns? Do you mean a "display me" button?
Basically I want to write a connector from a C++ struct to a DataTable without having to write repeating tedious code.

Why can't your structure know how to print it's contents the way you desire?

Because I am looking to keep the structure as simple as possible and to be able to display in different ways for different Grids...
But what could be "simpler" than your "structure" encapsulating the data it contains? Why should external processes be required to "know" the contents of your structure? This is one of the bigger benefits of OOP.

because "I want to be able to display in different ways for different Grids..."
I need these two aspects decoupled - that is also a good practice in programming...
This is a basic idea (it does not compile yet -- just an idea)

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
template<typename T, typename ...Cols>
class GridDisplayer
{
	std::vector<T>	lines;
	CJDGridCtrl& grid;
	std::vector<std::string> headers;
public:
	GridDisplayer(CJDGridCtrl& grid, 
		std::vector<T>&& lines,
		std::vector<std::string>&& headers) 
	: lines{std::move(lines)}, grid{grid}, headers{ std::move(headers)}
	{
		grid.SetColumnCount(sizeof...(Cols) + 1);
		grid.SetRowCount(lines.size() + 1);
		int col = 1;
		for( auto& str : headers)
		{
			auto head = JD::to_cstring(str);
			grid.SetItemText(0, col, head);
			++col;
		}
		for (int row = 0; row < lines.size(); ++row)
		{
			Display(row+1, 1, Cols...);
		}
	}
	template<typename Head, typename ...Rest>
	void Display(int display_row, int display_col, Head head, Rest... rest)
	{
		CString asText = print(head);
		grid.SetItemText(display_row, display_col, asText);
		Display(display_row, ++display_col, rest...);
	}
	template<typename Head>
	void Display(int display_row, int display_col, Head head)
	{

	}
	template<typename Col>
	void print(T::Col col)
	{
		
	}
};


How do I define print? I want it to deduce the type of the column and from there provide a default formatting. Later I will want to provide a formatting string or options...

I want to call this class the following way:



1
2
3
4
5
6
7
CJDGridCtrl grid;
std::vector<Category> lines = ORM::storage.get_all<Category>();
std::vector<std::string> headers{ "NAME", "REAL?" };


GridDisplayer<Category, &Category::m_name_id, &Category::m_real_expense_or_income>
		displayer{ grid, lines, headers };



Do I make my intentions clear? I know it does not compile yet... that's what I need help for


Thanks!
Have you considered getting something that compiles and produces the output you desire for a single type before you jump in to the use of templates?

Ok, will do...
Ok, I am on my way towards templates. I do need some help writing the following as a variadic template:

1
2
3
4
5
6
7
template<typename T, typename DataType, typename DataType2>
DataType getDataPointer(T& z, DataType T::*p, DataType2 T::*q )
{
	auto value = z.*p;
	auto other = z.*q;
	return value;
}



Got it ... look at this:

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
template<typename T>
CString print(const T& t )
{
	return JD::to_cstring(t);
}

template<typename T>
struct P
{
	CJDGridCtrl grid;

	std::vector<C> lines{ C{"val", "val2", "str", "str2", 5}, C{"val2", "val3", "str2", "str3", 6 } };

	template<typename ...DataTypes>
	void display(DataTypes... dts)
	{
		getData(0, 1, lines[0], dts...);
		getData(1, 1, lines[1], dts...);
	}

	template<typename DataType>
	void getData(int row, int col, T& z, DataType p)
	{
		auto value = z.*p;
		auto cs = print(value);
		grid.SetItemText(row + 1, col, cs);
	}


	template<typename DataType, typename ... RestDataTypes>
	void getData(int row, int col, T& z, DataType p, RestDataTypes...qs)
	{
		auto value = z.*p;
		auto cs = print(value);
		grid.SetItemText(row + 1, col, cs);
		getData(row, ++col, z, qs...);
	}

};


Now all I want is to move the DataTypes variadic from the display method to the P struct but how? Do I need tuples to store the pointer values? Can you show please?


This is quite a success but its not finished. Any comments greatly appreciated.

Juan
Hi,

Just some ideas, no idea about whether they will work or not, nor whether you had already thought of them.

This problem strikes me as being very similar to std::printf. At the bottom of this page there is a basic implementation. Obviously, I am sure you already know all about parameter packs.

https://en.cppreference.com/w/cpp/language/parameter_pack

Also, I am not sure what happens if one sends a parameter pack to std::make_tuple, it's already expecting a parameter pack, I wonder if the compiler is smart enough to expand the nested parameter pack.

The above page does mention it, in the pack expansion section:

cppreference wrote:

If a pack expansion is nested within another pack expansion, the parameter packs that appear inside the innermost pack expansion are expanded by it, and there must be another pack mentioned in the enclosing pack expansion, but not in the innermost one:


1
2
3
4
5
6
7
8
9
10
11
template<class ...Args>
    void g(Args... args) {
        f(const_cast<const Args*>(&args)...); 
 // const_cast<const Args*>(&args) is the pattern, it expands two packs
 // (Args and args) simultaneously
 
        f(h(args...) + args...); // Nested pack expansion:
   // inner pack expansion is "args...", it is expanded first
   // outer pack expansion is h(E1, E2, E3) + args..., it is expanded
   // second (as h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
}


Maybe you can get this to work for your own function, rather than make_tuple?

Good Luck, and hopefully this post has been helpful, even a little bit .... :+)
I am very close to what I need in the 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
template<typename T> 
class GridDisplayer
{
	std::vector<T>	lines;
	CJDGridCtrl& grid;
	std::vector<std::string> headers;
public:
	GridDisplayer(CJDGridCtrl& grid, 
		std::vector<T>&& lines,
		std::vector<std::string>&& headers) 
	: lines{std::move(lines)}, grid{grid}, headers{ std::move(headers)}
	{
		grid.SetColumnCount( headers.size() + 1);
		grid.SetRowCount(lines.size() + 1);
		int col = 1;
		for( auto& str : headers)
		{
			auto head = JD::to_cstring(str);
			grid.SetItemText(0, col, head);
			++col;
		}
	}

	template<typename ...DataTypes>
	void display(DataTypes... dts)
	{
		for (int i = 0; i < lines.size(); ++i)
		{
			printDataInGrid(i, 1, lines[i], dts...);
		}
	}

private:
	template<typename DataType>
	void printDataInGrid(int row, int col, T& z, DataType p)
	{
		auto value = z.*p;
		auto cs = format(value);
		grid.SetItemText(row + 1, col, cs);
	}


	template<typename DataType, typename ... RestDataTypes>
	void printDataInGrid(int row, int col, T& z, DataType p, RestDataTypes...qs)
	{
		auto value = z.*p;
		auto cs = format(value);
		grid.SetItemText(row + 1, col, cs);
		printDataInGrid(row, ++col, z, qs...);
	}
	template<typename T>
	CString format(const T& t)
	{
		return JD::to_cstring(t);
	}
};


This code is called like so:

1
2
3
4
5
CJDGridCtrl grid;
std::vector<C> lines{ C{"val", "val2", "str", "str2", 5}, C{"val2", "val3", "str2", "str3", 6 } };
std::vector<std::string> headers{ "NAME", "REAL?" };
GridDisplayer<C> displayer( grid, std::move(lines), std::move( headers) );
displayer.display(&C::val, &C::i, &C::str);



It would have been nice to be able to define the columns in the constructor and store them for later use, as in the following struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T, typename ...Cols>
struct Q
{
	std::tuple<Cols...> tupleOfCols;
	Q(Cols...cls) : tupleOfCols(cls...)
	{}

	void getTupleItems()
	{
		constexpr const int size = std::tuple_size<tupleOfCols>;
		for (int i = 0; i < size; ++i)
		{
			auto element = std::tuple_element<i, tupleOfCols>;
		}
	}
};


but have not found a way for the class to deduce the types from the constructor arguments!

Any ideas there?
Last edited on
Finished! I came up with 2 grid displayers (one where all of your columns are from a same table and one for a select with joins). Here is the code and it works... open for suggestions though - it can always become better...

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
#pragma once



template<typename T, int MaxCol>
class JoinedGridDisplayer
{
	using Container = std::vector<std::remove_reference_t<T>>;
	CJDGridCtrl&				grid;
	Container					lines;
	std::vector<std::string>	headers;
public:
	JoinedGridDisplayer(CJDGridCtrl& grid,
		Container&& lines_,
		std::vector<std::string>&& headers_)
		: lines{ std::move(lines_) }, grid{ grid }, headers{ std::move(headers_) }
	{
		grid.SetColumnCount(headers.size() + 1);
		grid.SetRowCount(lines.size() + 1);
		grid.SetFixedRowCount();
		grid.SetFixedColumnCount();
		grid.SetHeaderSort(true);
		grid.SetSingleRowSelection(true);

		int col = 1;
		for (auto& str : headers)
		{
			auto head = JD::to_cstring(str);
			grid.SetItemText(0, col, head);
			++col;
		}
	}

	void display()
	{
		for (int i = 0; i < lines.size(); ++i)
		{
			PrintDataInGrid<0, Container>::Apply(i, lines, grid);
		}
		for (int i = 0; i < headers.size(); ++i)
		{
			grid.AutoSizeColumn(i + 1);	// skip vertical headers
		}
	}

private:
	template<int Col, typename Container>
	struct PrintDataInGrid
	{
		static void Apply(int row, const Container& z, CJDGridCtrl& grid)
		{
			auto value = std::get<Col>(z[row]);
			auto cs = format(value);
			grid.SetItemText(row + 1, Col + 1, cs);
			PrintDataInGrid<Col+1, Container>::Apply(row, z, grid);
		}
	};

	template<typename Container>
	struct PrintDataInGrid<MaxCol, Container>
	{
		static void Apply(int row, const Container& z, CJDGridCtrl& grid)
		{
		}
	};

	template<typename T>
	static CString format(const T& t)
	{
		return JD::to_cstring(t);
	}

	template<typename T>
	static CString format(const std::shared_ptr<T>& t)
	{
		if (t)
		{
			return JD::to_cstring(*t);
		}
		return L"";
	}

};


and the one table non-join grid displayer:

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
#pragma once



template<typename T> 
class GridDisplayer
{
	CJDGridCtrl&				grid;
	std::vector<T>				lines;
	std::vector<std::string>	headers;
public:
	GridDisplayer(CJDGridCtrl& grid, 
		std::vector<T>&& lines_,
		std::vector<std::string>&& headers_) 
	: lines{std::move(lines_)}, grid{grid}, headers{ std::move(headers_)}
	{
		grid.SetColumnCount( headers.size() + 1);
		grid.SetRowCount(lines.size() + 1);
		grid.SetFixedRowCount();
		grid.SetFixedColumnCount();
		grid.SetHeaderSort(true);
		grid.SetSingleRowSelection(true);

		int col = 1;
		for( auto& str : headers)
		{
			auto head = JD::to_cstring(str);
			grid.SetItemText(0, col, head);
			++col;
		}
	}

	template<typename ...DataTypes>
	void display(DataTypes... dts)
	{
		for (int i = 0; i < lines.size(); ++i)
		{
			printDataInGrid(i, 1, lines[i], dts...);
		}
		for (int i = 0; i < headers.size(); ++i)
		{
			grid.AutoSizeColumn(i+1);	// skip vertical headers
		}
	}

private:
	template<typename DataType>
	void printDataInGrid(int row, int col, T& z, DataType p)
	{
		auto value = z.*p;
		auto cs = format(value);
		grid.SetItemText(row + 1, col, cs);
	}


	template<typename DataType, typename ... RestDataTypes>
	void printDataInGrid(int row, int col, T& z, DataType p, RestDataTypes...qs)
	{
		auto value = z.*p;
		auto cs = format(value);
		grid.SetItemText(row + 1, col, cs);
		printDataInGrid(row, ++col, z, qs...);
	}
	template<typename T>
	CString format(const T& t)
	{
		return JD::to_cstring(t);
	}
	template<typename T>
	static CString format(const std::shared_ptr<T>& t)
	{
		if (t)
		{
			return JD::to_cstring(*t);
		}
		return L"";
	}
};



Any observations?

Regards,
Juan
Topic archived. No new replies allowed.