0

I am writing an ODBC wrapper library, and I'm trying to find a nice way of saving the results of a select query. This would preferably be by using tuples so the SQL types are respected in the C++ code. But I am stuck at trying to "map" the result table's column names to these tuple elements.

What I am trying to do is the following:

template<typename... _TArgs>
class result_row {
private:
    std::tuple<std::optional<_TArgs>...> m_columns; // std::optional allows for NULL values.
    static constexpr size_t m_column_count = sizeof...(_TArgs);

    // The column names will be filled out after the query is executed.
    std::array<std::string, m_column_count> m_column_names;
public:
    template<typename _Ty>
    _Ty get(std::string col_name) {
        // How do I find the corresponding column here?
    }
}

Is there any neat way to accomplish this, or would I have to just save all results as strings and then force the user to convert to the desired types?

NollerNiller
  • 25
  • 1
  • 5
  • 1
    Are you aware that in C++ the types of all objects must be known at compile time, and each instance of this tuple wrapper is a separate and a distinct type? And you can only put objects of a single type into a given map? Are you familiar with `std::shared_ptr` and virtual classes? If so, then the solution should be obvious; if not, see your C++ textbook for more information on this; unfortunately this is a large topic that can't be fully explained in a brief answer on Stackoverflow. – Sam Varshavchik May 31 '22 at 11:08
  • @SamVarshavchik I am fully aware of C++'s type system, and if you look at the pseudo-code I wrote the library requires the user to know which types the output values will have. Additionally, the user has to know which type they want to extract when calling the get-method. A result table would be a vector of these result_row objects, which would ensure that they were the same type. Regarding maps having only a single value-type, this is the essence of my question: How would one implement a "map"-like structure with the keys being mapped to tuple elements instead of a normal map? – NollerNiller May 31 '22 at 11:32
  • @NollerNiller that sounds like a DOM structure – Swift - Friday Pie May 31 '22 at 12:08
  • @Swift-FridayPie I'm not sure that is what I need.. Doesn't DOM follow a tree layout? If so, I don't think it is the right solution since I only need a list of objects that all own their own tuple (with the same type ofc) – NollerNiller May 31 '22 at 12:29
  • This requires some expert-level template-fu, basically in `get()` to go through each tuple element, and extract any that have an identical `_Ty` (which, by the way, is undefined behavior since underscore+uppercase symbols are reserved for exclusive use by the C++ library), then once found compare the `col_name` with the real column name, return upon a match, throw an exception if no matches found. That's the basic outline of how this is done. – Sam Varshavchik May 31 '22 at 12:31

1 Answers1

0

AFAIU get tries to bind runtime info (column name) to compile-time info (column type)

possible options:

  1. use std::variant<_TArgs...> as a return type of get see C++11 way to index tuple at runtime without using switch

  2. the user provides _Ty type and get checks at runtime if _Ty matches the actual type of the column. if yes - return the value and if not - throw an exception

both options:

    template<typename... _TArgs>
    class result_row {
    private:
    std::tuple<_TArgs...> m_columns;
    static constexpr size_t m_column_count = sizeof...(_TArgs);

    // The column names will be filled out after the query is executed.
    std::array<std::string, m_column_count> m_column_names;

    template <size_t N = 0>
    std::variant<_TArgs...> runtime_get(size_t idx) {
        if (N == idx) {
            return std::variant<_TArgs...>(std::in_place_index_t<N>(), std::get<N>(m_columns));
        }
        if constexpr (N + 1 < m_column_count) {
            return runtime_get<N + 1>(idx);
        }
        return std::variant<_TArgs...>();
    }
    
    template <class T, size_t N = 0>
    T runtime_get_typed(std:: size_t idx) {
        if  (N == idx) {
            if constexpr (std::is_same_v<T, std::tuple_element_t<N, std::tuple<_TArgs...>>>)
            {
                return std::get<N>(m_columns);
            }
            else
            {
                throw std::runtime_error("invalid column type");
            }
        }
        if constexpr (N + 1 < m_column_count) {
            return runtime_get_typed<T, N + 1>(idx);
        }
        return T();
    }

    public:
    std::variant<_TArgs...> get(std::string col_name) {
        auto it = std::find(std::begin(m_column_names), std::end(m_column_names), col_name);
        if (it == std::end(m_column_names)) throw std::runtime_error("invalid column name: " + col_name);
        return runtime_get(std::distance(std::begin(m_column_names), it));
    }
    template <class T>
    T get_typed(std::string col_name) {
        auto it = std::find(std::begin(m_column_names), std::end(m_column_names), col_name);
        if (it == std::end(m_column_names)) throw std::runtime_error("invalid column name: " + col_name);
        return runtime_get_typed<T>(std::distance(std::begin(m_column_names), it));
    }
    };  

a working example for both options

Alexander
  • 698
  • 6
  • 14