Chiharu の日記

絵描き C/C++ プログラマーの日記です。

バリアントの配列

バリアントの配列を文字列から生成して遊んでみました。テンプレートを使うと型情報による switch & case がなくて気持ちいいですね。C++17 以降の環境なら Variant には std::variant<> を使うべきでしょうけれども。

#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <stdexcept>

// boost::lexical_cast<> みたいなもの
struct Lexer {
 
 template <class Type>
 static Type to(const std::string& string)
 {
  std::stringstream stream(string);
  Type value;
  
  stream >> value;
  if (stream.fail() || stream.peek() != std::stringstream::traits_type::eof()) {
   throw std::runtime_error("Type missmatch error");
  }
  
  return value;
 }
 
};

template <>
bool Lexer::to<bool>(const std::string& string)
{
 std::stringstream stream(string);
 bool value;
 
 stream >> std::boolalpha >> value;
 if (stream.fail()) {
  switch (to<int>(string)) {
  case 0:
   value = false;
   break;
   
  case 1:
   value = true;
   break;
   
  default:
   throw std::runtime_error("Type missmatch error");
  }
 }
 
 return value;
}

// TODO : int, float を特殊化して stol, stof で最適化する。

// 任意の型のデータ
struct Variant {
 
 template <class Type> void set(Type value) = delete;
 template <class Type> decltype(auto) get() const = delete; // いずれ参照も返す
 
private:
 enum class Type {
  boolean, integer, real
 } type = Type::boolean;
 
 union {
  bool boolean = false;
  int integer;
  float real;
 };
 
 void validate(Type target) const
 {
  if (type != target) {
   throw std::runtime_error("Type missmatch error");
  }
 }
 
};

template <> void Variant::set<bool>(bool value) { type = Type::boolean, boolean = value; }
template <> void Variant::set<int>(int value) { type = Type::integer, integer = value; }
template <> void Variant::set<float>(float value) { type = Type::real, real = value; }

template <> decltype(auto) Variant::get<bool>() const { validate(Type::boolean); return boolean; }
template <> decltype(auto) Variant::get<int>() const { validate(Type::integer); return integer; }
template <> decltype(auto) Variant::get<float>() const { validate(Type::real); return real; }

// 任意の型のデータ配列
class VariableArray {
 
private:
 explicit VariableArray(std::vector<Variant>&& converted):
  array(std::move(converted))
 {
 }
 
public:
 auto& get(std::size_t index) const { return array[index]; }
 auto size() const { return array.size(); }
 
public:
 template <class Type>
 static std::shared_ptr<VariableArray> create(const std::vector<std::string>& source)
 {
  std::vector<Variant> converted;
  for (auto& item : source) {
   converted.emplace_back();
   converted.back().set(Lexer::to<Type>(item));
  }
  
  struct VariableArrayHelper : public VariableArray {
   VariableArrayHelper(std::vector<Variant>&& converted):
    VariableArray(std::move(converted)) {}
  };
  auto instance = std::make_shared<VariableArrayHelper>(std::move(converted));
  return std::move(instance);
 }
 
private:
 std::vector<Variant> array;
 
};

// テスト
#include <cassert>

int main(int argc, char* argv[])
{
 // boolean
 auto barray = VariableArray::create<bool>({ "true", "false", "1", "0" });
 assert(barray->size() == 4);
 assert(barray->get(0).get<bool>() == true);
 assert(barray->get(1).get<bool>() == false);
 assert(barray->get(2).get<bool>() == true);
 assert(barray->get(3).get<bool>() == false);
 
 auto exception_occured = false;
 try {
  barray->get(0).get<int>();
 } catch (std::exception& ex) {
  exception_occured = true;
 }
 
 assert(exception_occured);
 
 // integer
 auto iarray = VariableArray::create<int>({ "50", "34" });
 assert(iarray->size() == 2);
 assert(iarray->get(0).get<int>() == 50);
 assert(iarray->get(1).get<int>() == 34);
 
 exception_occured = false;
 try {
  iarray->get(0).get<bool>();
 } catch (std::exception& ex) {
  exception_occured = true;
 }
 
 assert(exception_occured);
 
 // real
 auto farray = VariableArray::create<float>({ "50", "34.2" });
 assert(farray->size() == 2);
 assert(farray->get(0).get<float>() == 50.f);
 assert(farray->get(1).get<float>() == 34.2f);
 
 exception_occured = false;
 try {
  farray->get(0).get<bool>();
 } catch (std::exception& ex) {
  exception_occured = true;
 }
 
 assert(exception_occured);
 
 return 0;
}