/ru_c++/thread №52

Home | Return to board | Help


10/09/2023 22:16 Thread №52 [Return to board] [Delete]

std::variant vs наследование

std::variant vs наследование

В последнее время предпочитаю использовать std::variant, а не наследование, когда мне нужен тип-сумма.

Но бывают ситуации, когда из-за std::variant ходишь по тонкому льду.

Например, хочу создать типы данных для описания выражений в языке программирования:
10/09/2023 22:17 Post №244 [Delete]

struct FunctionCall;

/**
 * Name of a variable.
 */
using Identifier = std::string;

/**
 * Describes expression.
 */
using Expression = std::variant<glm::dvec3, double, Identifier, FunctionCall>;

/**
 * Describes a function call.
 */
struct FunctionCall {
	Identifier name;
	std::vector<Expression> arguments;
};

10/09/2023 22:18 Post №245 [Delete]
Expression - это вектор, скаляр, имя переменной или вызов функции. А FunctionCall (вызов функции) содержит вектор из Expression. Если бы в FunctionCall был не вектор, а чистый Expression, то такой код было бы нельзя скомпилировать, потому что из-за циклической зависимости, нельзя определить размеры типов.

Далее, используются конструкции типа:


	if (const glm::dvec3 *value = std::get_if<glm::dvec3>(&expression)) {
		
	} else if (const double *value = std::get_if<double>(&expression)) {
		
	} else if (const Identifier *identifier = std::get_if<Identifier>(&expression)) {
		
	} else if (const FunctionCall *functionCall = std::get_if<FunctionCall>(&expression)) {
		
	} else {
		throw std::runtime_error("Unknown variant");
	}

10/09/2023 22:21 Post №246 [Delete]
Считаю, что это удобнее наследования в данном случае.

Наследование предпочел бы, когда список операций с типом можно предсказать заранее.
10/09/2023 22:26 Post №247 [Delete]
Когда создавал типы для дерева данных, предпочел использовать наследование:


/**
 * Tree-like data to be rendered with a template.
 */
class TemplateItem {
public:
  using iterator = std::vector<std::shared_ptr<TemplateItem>>::iterator;
  using const_iterator =
      std::vector<std::shared_ptr<TemplateItem>>::const_iterator;

public:
  virtual ~TemplateItem() = default;
  /**
   * @return child item by it's name (if the item has a map of children).
   */
  virtual const TemplateItem &child(const std::string &name) const = 0;
  /**
   * @return string value for the item (if the item has such value).
   */
  virtual std::string value() const = 0;
  /**
   * @return iterator for array of childred (if the item has array of children).
   */
  virtual iterator begin() = 0;
  /**
   * @return iterator for array of childred (if the item has array of children).
   */
  virtual iterator end() = 0;
  /**
   * @return iterator for array of childred (if the item has array of children).
   */
  virtual const_iterator begin() const = 0;
  /**
   * @return iterator for array of childred (if the item has array of children).
   */
  virtual const_iterator end() const = 0;
};

10/09/2023 22:27 Post №248 [Delete]

/**
 * Empty template item. No value, no children.
 */
class Empty : public TemplateItem {
public:
  const TemplateItem &child(const std::string &) const;
  std::string value() const;
  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;
};

/**
 * String template item. It has a value and doesn't have children.
 */
class String : public TemplateItem {
public:
  String(const std::string &v);
  const TemplateItem &child(const std::string &) const;
  std::string value() const;
  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;

private:
  std::string v;
};

10/09/2023 22:28 Post №249 [Delete]

/**
 * Helper function to access a nested child by path.
 */
const TemplateItem &getDeepChild(const TemplateItem &root,
                                 const std::vector<std::string> &path);

/**
 * Object template item. It doesn't have a value and has children accessible by
 * names.
 */
class Object : public TemplateItem {
public:
  using InitializerList = std::initializer_list<
      std::pair<const std::string, std::shared_ptr<TemplateItem>>>;
  using Map = std::map<std::string, std::shared_ptr<TemplateItem>>;

public:
  Object(InitializerList data);
  Object(const Map &data);
  Object(Map &&data);
  const TemplateItem &child(const std::string &name) const;
  std::string value() const;
  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;

private:
  std::map<std::string, std::shared_ptr<TemplateItem>> data;
};

10/09/2023 22:29 Post №250 [Delete]

/**
 * Array template item. It doesn't have a value and has childern accessible as
 * an array.
 */
class Array : public TemplateItem {
public:
  using InitializerList = std::initializer_list<std::shared_ptr<TemplateItem>>;
  using Vector = std::vector<std::shared_ptr<TemplateItem>>;

public:
  Array(InitializerList data);
  Array(const Vector &data);
  Array(Vector &&data);
  const TemplateItem &child(const std::string &name) const;
  std::string value() const;
  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;

private:
  Vector data;
};

10/09/2023 22:30 Post №251 [Delete]
Когда использую наследование, почти всегда прихожу к необходимости использовать std::shared_ptr.
11/09/2023 18:23 Post №254 [Delete]
>>245
>Expression - это вектор, скаляр, имя переменной или вызов функции. А FunctionCall (вызов функции) содержит вектор из Expression.
Хорошо бы пояснить на примере конкретной задачи, где такое может оказаться полезным.
11/09/2023 23:19 Post №258 [Delete]
>>254
>Хорошо бы пояснить на примере конкретной задачи, где такое может оказаться полезным.
На кислице был мой тред про софтовый рендеринг. Сделал такой формат описания моделей:

{
	"dots": {
		"house.a0": [-30, 0, -10],
		"house.b0": [-20, 0, -10],
		"house.c0": [-20, 0, -16],
		"house.a1": [-30, 3, -10],
		"house.b1": [-20, 3, -10],
		"house.c1": [-20, 3, -16],
		...
	},
	"lines": [
		["house.a0", "house.b0", "house.c0"],
		["house.a1", "house.b1", "house.c1"],
		["house.a0", "house.a1"],
		...
	]
}

Тут рисуются контуры 3 стен дома. Неудобно. Чтобы его повернуть или передвинуть, надо менять координаты всех точек. А там еще есть окна и дверь.
Хочу, чтобы можно было задать 1 точку и 3 вектора, а остальное описать формулами:

{
	"vars": {
		"house.base": [-30, 0, -10],
		"house.size_a": [10, 0, 0],
		"house.size_b": [0, 0, -6],
		"house.size_c": [0, 3, 0],
		
		"house.dot.a": ["sum", "house.base", "house.size_a"],
		"house.dot.b": ["sum", "house.base", "house.size_b"],
		"house.dot.c": ["sum", "house.base", "house.size_c"],
		...
		"window.dot.a": ["sum", "house.base", ["mul", "house.size_a", 0.25], ["mul", "house.size_c", 0.25]],
		...
	},
        ...
}