force fail general template but allow specialisations, plus "method exists dispatching"

  c++, templates

Some questions for improvements on template / meta programming techniques.

Motivation for the code below is conveniently extracting correctly typed data from an sql db driver with a c-style API. Rows of the result set are returned one by one as char**. I simulate that with some array literals below.

I wrap this char** row data in a class to provide type extraction. Also there is a templated single_column() function which can return a singly typed column in a container of the caller’s choice.

The code works as shown. Questions are:

  • blocking the non-specialised version of row::get<ValueType> with a static_cast<void> seems like a hack. Is there a better way? I don’t think the answer is to simply write get_int(), get_long() etc, because we want generic code like single_column() to just select the specified getter.
  • the type-trait "method exists dispatching" to detect presence of push_back() with if constexpr works fine. Is there a more elegant way using C++17? ie without c++20 concepts?

Many thanks

#include <cstddef>
#include <iostream>
#include <string>
#include <unordered_set>
#include <vector>

// is there a more direct way (without using c++20 concepts)
template <typename C, typename = void>
struct has_push_back : std::false_type {};

template <typename C>
struct has_push_back<
    C, std::void_t<decltype(std::declval<C>().push_back(std::declval<typename C::value_type>()))>>
    : std::true_type {};

class row {
public:
  explicit row(const char** row) : row_(row) {}

  template <typename ValueType>
  ValueType get(unsigned idx) const {
    // should always fail! user must call an existing specialisation!
    // is there a "better" way?
    return static_cast<void>(row_[idx]); // NOLINT ptr arith
  }

  template <>
  [[nodiscard]] int get<int>(unsigned idx) const {
    return std::stoi(row_[idx]); // NOLINT ptr arith
  }

  template <>
  [[nodiscard]] long get<long>(unsigned idx) const {
    return std::stol(row_[idx]); // NOLINT ptr arith
  }

  template <>
  [[nodiscard]] std::string get<std::string>(unsigned idx) const {
    return row_[idx]; // NOLINT ptr arith
  }

  template <>
  [[nodiscard]] const char* get<const char*>(unsigned idx) const {
    return row_[idx]; // NOLINT ptr arith
  }

private:
  const char** row_;
};

template <typename ContainerType>
ContainerType single_column(const std::vector<row>& rows, unsigned col = 0) {
  ContainerType values;
  using ValueType = typename ContainerType::value_type;
  for (auto&& row: rows) {
    if constexpr (has_push_back<ContainerType>::value) // is this "best" way?
      values.push_back(row.get<ValueType>(col));
    else
      values.insert(row.get<ValueType>(col));
  }
  return values;
}

int main() {

  // dealing with this as c-arrays because that is how it comes from db driver
  // represents raw data coming back from db

  // NOLINTNEXTLINE C-arrays
  const char* data[][3] = {
      {
          "1-some text",
          "122222222",
          "1333333333333333",
      },
      {
          "2-some text",
          "222222222",
          "2333333333333333",
      },
      {
          "2-some text", // dupe value
          "322222222",
          "3333333333333333",
      },
  };

  std::vector<row> rows; // simulationg rows being returned the db server
  for (auto& r: data) rows.emplace_back(r);

  // now retrieve column-wise as correct types into vectors
  for (auto&& e: single_column<std::vector<std::string>>(rows, 0)) std::cout << e << ",";
  std::cout << std::endl;

  for (auto&& e: single_column<std::vector<int>>(rows, 1)) std::cout << e << ",";
  std::cout << std::endl;

  for (auto&& e: single_column<std::vector<long>>(rows, 2)) std::cout << e << ",";
  std::cout << std::endl;

  // now into unordered_sets
  for (auto&& e: single_column<std::unordered_set<std::string>>(rows, 0)) std::cout << e << ",";
  std::cout << std::endl;

  for (auto&& e: single_column<std::unordered_set<int>>(rows, 1)) std::cout << e << ",";
  std::cout << std::endl;

  for (auto&& e: single_column<std::unordered_set<long>>(rows, 2)) std::cout << e << ",";
  std::cout << std::endl;

  // compile error - the static_cast<void> fails: works, but is there a "better" way
  // double d = rows[0].get<double>(1);
}

Source: Windows Questions C++

LEAVE A COMMENT