Table of different types

I'm trying to make a table where each column has its own type (which is known at compile time), with a get(columm, row) function where the return type is based off the type of the column that it is accessing (and hence known at compile time). I started writing things with variadic templating, vectors and tuples, but I really had no idea how to start and wasn't actually getting anywhere. Any suggestions?
You could do it like this:
1
2
3
4
5
6
7
enum FieldType {ftString, ftBool, ftInt, ftDouble}; // etc....

struct Field
{
  FieldType Type;
  string value;
};

Then you can create a class Table and store the fields somehow - maybe in a vector and write functions like:
bool GetAsBool(int row, int col), int GetAsInt(int row, int col) etc.

Not the most elegant way but quite easy to code.
I agree that would be possible, although it was what I was trying to avoid doing, as I'd like to be able to create and access a table like this:
1
2
3
4
5
6
Table<std::string, double, bool> table;
table.addRow("Thing1",2.3,true);
table.addRow("Thing2",7.3,false);
table.addRow(1,3,9); //COMPILER ERROR: arguments 1 and 3 are wrong type
std::string name = table.get(0, 1); //"Thing2"
std::string name = table.get(1,0); //COMPILER ERROR: table(1) is of type double. 

I have a feeling the get function may have to be written table.get<0>(1) so that it can be done at compile time, but again, I'm really not sure.

The reason for this is that I'd like the Table class to be able to be used generically and like a normal container, rather than having to write structs for it whenever I want to use a new combination of types.
Last edited on
Another idea:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum FieldType {ftString, ftBool, ftInt, ftDouble}; // etc....

class Table
{
public:
  void AddField (FieldType type);
  bool AsBool (int row, int col); 
  void AsBool (int row, int col, bool value) {//convert value to string and sore it in data;}
  int  AsInt (int row, int col); {//convert data to int}
private:
  vector<FieldType>  fields;
  vector<string> data;
};


  Table table;
  table.AddField (ftBool);
  table.AddField (ftInt);
  table.AsBool (0, 0, "true");
  int val = table.AsInt (0,1);


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
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

template < typename... T > using table_type = std::vector< std::tuple<T...> > ;

template < typename... T, typename... ARGS > void add_row( table_type<T...>& table, ARGS&&... args )
{ table.push_back( std::tuple<T...>( std::forward<ARGS>(args)... ) ) ; }

// EDIT: typo, should return reference to element
template < typename T, typename... U > T& get( table_type<U...>& table, std::size_t pos )
{ return std::get<T>( table[pos] ) ; }

template < typename T, typename... U > const T& get( const table_type<U...>& table, std::size_t pos )
{ return std::get<T>( table[pos] ) ; }

template < std::size_t N, typename... U  >
typename std::tuple_element< N, std::tuple<U...> >::type& get( table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }

template < std::size_t N, typename... U  >
const typename std::tuple_element< N, std::tuple<U...> >::type& get( const table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }

int main()
{
    table_type< std::string, double, bool > table ;
    add_row( table, "hello", 2.3, true ) ;
    add_row( table, "world", -21.4, false ) ;

    std::cout << get<std::string>( table, 0 ) << '\n' // hello
              << get<0>( table, 1 ) << '\n' // world
              << get<double>( table, 1 ) << '\n' // -21.4
              << std::boolalpha << get<2>( table, 0 ) // true
              << "\n--------------\n" ;

    for( const auto& tup : table )
        std::cout << std::get<std::string>(tup) << ' ' << std::get<double>(tup) << ' ' << std::get<bool>(tup) << '\n' ;
}

http://coliru.stacked-crooked.com/a/9d1f0108990e9306
http://rextester.com/PKKCZ47096
Last edited on
JLBorges, I had a look through your code, and added my own class based version to it, leaving yours in for comparsion:
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
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

// ORIGINAL
template < typename... T > using table_type = std::vector< std::tuple<T...> > ;

template < typename... T, typename... ARGS > void add_row( table_type<T...>& table, ARGS&&... args )
{ table.push_back( std::tuple<T...>( std::forward<ARGS>(args)... ) ) ; }

template < typename T, typename... U > T get( table_type<U...>& table, std::size_t pos )
{ return std::get<T>( table[pos] ) ; }

template < typename T, typename... U > const T& get( const table_type<U...>& table, std::size_t pos )
{ return std::get<T>( table[pos] ) ; }

template < std::size_t N, typename... U  >
typename std::tuple_element< N, std::tuple<U...> >::type& get( table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }

template < std::size_t N, typename... U  >
const typename std::tuple_element< N, std::tuple<U...> >::type& get( const table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }

//CLASS
template <typename... T>
class Table {
public:
    Table() = default;
    
    template < std::size_t Column >
    typename std::tuple_element < Column, std::tuple<T...> >::type& get(std::size_t row)
    {
        return std::get<Column> (m_Table[row]);
    }
    
    void addRow( T&&... args )
    {
        m_Table.push_back( std::tuple<T...>(std::forward<T>(args)...) ) ;
    }
private:
    std::vector<std::tuple<T...>> m_Table;
};

int main()
{
    //ORIGINAL
    table_type< std::string, double, bool > table ;
    add_row( table, "Hello", 2.3, true ) ;
    add_row( table, "world", -21.4, false ) ;

    std::cout << get<std::string>( table, 0 ) << '\n' // hello
              << get<0>( table, 1 ) << '\n' // world
              << get<double>( table, 1 ) << '\n' // -21.4
              << std::boolalpha << get<2>( table, 0 ) // true
              << "\n--------------\n" ;

    for( const auto& tup : table ) {
        std::cout << std::get<std::string>(tup) << ' ' << std::get<double>(tup) << ' ' << std::get<bool>(tup) << '\n' ;
    }
    
    //CLASS
    std::cout<< "----------------\n";
    Table< std::string, double, bool > table2;
    table2.addRow( "Hello", 2.3, true );
    table2.addRow( "world", -21.4, false);
    
    std::cout << table2.get<0>(0) << '\n' // hello
              << table2.get<0>(1) << '\n' // world
              << table2.get<1>(1) << '\n' //-21.4
              << std::boolalpha << table2.get<2>(0) << '\n' //true
              << "\n-------------\n" ;
    return 0;
}

http://coliru.stacked-crooked.com/a/07c03a5946f07061
I do still have a few questions about this code though, for example, why in the add_row function, did you use both T and ARGS, when they are meant to be the same types? Also, why do you need typename at the start of lines 19 and 23?

Thomas1965, thank you for those ways of doing it, which are most likely what I will fall back to if I can't get templating to work. However this is partially an academic exercise of me trying to understand the usage of more complicated templating code.
Last edited on
> why in the add_row function, did you use both T and ARGS, when they are meant to be the same types?

They need not necessarily be the same types. For instance, on the instantiation in line 66:
The pack T... has std::string, double, bool
The pack ARGS... has const char[6], double, bool
(implicit conversion: array-to-pointer, const char* to std::string)


> why do you need typename at the start of lines 19 and 23?

With C++14, I could have used the placeholder type auto instead:
1
2
3
4
5
template < std::size_t N, typename... U  > auto& get( table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }

template < std::size_t N, typename... U  > auto& get( const table_type<U...>& table, std::size_t pos )
{ return std::get<N>(table[pos]) ; }


However, I avoid placeholder return type in declarations of functions that are part of the interface of a component.

The name of the return type is a dependent name; so we need to disambiguate by qualifying it with typename.
The details: http://www.cplusplus.com/forum/general/169739/#msg848354
Last edited on
They need not necessarily be the same types. For instance, on the instantiation in line 66:
The pack T... has std::string, double, bool
The pack ARGS... has const char[6], double, bool
(implicit conversion: array-to-pointer, const char* to std::string)

And yet when I use T&&... in my addRow function, with T having already been defined as the parameter pack for the class, changing one of the doubles that I was passing into addRow into an int still works becuase it is convertible, whereas changing it to a string leads to an error, so I'm not quite following your logic here.


However, I avoid placeholder return type in declarations of functions that are part of the interface of a component.

The name of the return type is a dependent name; so we need to disambiguate by qualifying it with typename.
The details: http://www.cplusplus.com/forum/general/169739/#msg848354

Thank you, that's very helpful. I'd only ever seen typename in the classic template<typename T> and hence didn't realise it worked this way.
Last edited on
> And yet when I use T&&... in my addRow function, with T having already been defined as the
> parameter pack for the class, changing one of the doubles that I was passing into addRow into
> an int still works becuase it is convertible

In the class member function, the type of Table::m_Table does not require template argument deduction.
In the non-member function, the type of the table has to be deduced.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
#include <tuple>
#include <vector>

template < typename... T > using table_type = std::vector< std::tuple<T...> > ;

template < typename... T, typename... ARGS > void add_row_flexible( table_type<T...>& table, ARGS&&... args )
{ table.push_back( std::tuple<T...>( std::forward<ARGS>(args)... ) ) ; }

template < typename... T > void add_row_rigid( table_type<T...>& table, T&&... args )
{ table.push_back( std::tuple<T...>( std::forward<T>(args)... ) ) ; }

int main()
{
    struct A { A(bool) {} /* converting constructor */ } ;

    table_type< A, double, bool > table ;

    add_row_flexible( table, false, 2.3, true ) ; // fine
    add_row_flexible( table, "hello", -21.4, false ) ; // fine

    add_row_rigid( table, false, 2.3, true ) ; // *** error: conflicting types for parameter 'T'
}

http://coliru.stacked-crooked.com/a/49443593ff2675be
Last edited on
Ahh, okay thank you that makes sense.
Topic archived. No new replies allowed.