LINUX.ORG.RU

Никак не могу напиать конструктор статично полиморфного класса.

 ,


0

2

Например, есть базовый шаблонный класс в котором содеражатся настройки:

template<int nd>
struct config_t<nd> {};
Есть класс, содержащий данные, который дожен инициироваться с помощью config_t:
template<typename tp>
struct data_t {};
Эти классы упакованы в варианты:
typedef std::variant<config_t<1>, config_t<2>, ...> сonfig_var;
typedef std::variant<data_t<float>, data_t<double>, ...> data_var;
Эти варианты хранятся в холдере где для них делается выделение памяти, инициализация и пр:
struct config_holder : config_var {
	struct {
		// stuff
	} control;
	
	config_holder (int nd, ...); // nd -> config_t<nd>
};

struct data_holder : data_var {
	struct {
		// memory management & stuff
	} control;
	
	data_holder (std::string &&type, const config_var &cfg, ...) { // data_holder -> data_t<type>
		// What to do in the Kathmandu?..
	}
};

Смысл конструкции в том, что config_holder кодирует некоторые операции, а data_holder хранит данные (над которыми проводятся эти операции) и иницируется строкой задающей тип и конфигом. Базовые шаблонные типы (которые с *_t) потом передаются в числодробильный бэкэнд. А *_holder это интерфейсы, с которыми взаимодействует пользователь.

И вот тут я застрял. Следуя совету intelfx, у меня получилось написать конструктор для config_holder, но с data_holder не понимаю как подступиться...

Следуя той же идеологии, я могу иницировать data_holder для каждого допустимого типа, переданного строкой, но не понимаю, как дальше сконфигурировать его c помощью config_holder... Пробовал делать хелпер, который для каждого заданного строкой типа вызывет свой визитор и из него возвращать значения, но не срабатывает. Как такое всё таки реализовать, или как переписать что бы работало?

$Cast AntonI; $Cast mittorn;

★★★★★

Последнее исправление: thunar (всего исправлений: 4)

Среда - маленькая пятница. Сложно...
Для каждого типа из variant можно по аналогии с конфигом написать обработчик. Ну и вызыать их перебором (наверняка в STL уже есть для такого интерфейс)
Однако, сам конструктор ты судя по всему не можешь проинициализировать из std::variant - делай так же фабрику, которая «достанет» тип из config_var и вызовет приватный конструктор data_var.
А сам приватный конструктор пусть будет шаблонным принимая один из этих config_t.

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

Что-то все мозги сломал... Пытаюсь из фабрики вернуть нужный класс и получаю:

error: conversion from ‘data_var’ {aka ‘std::variant<data_t<float>, data_t<unsigned int> >’} to non-scalar type ‘data_holder’ requested
   52 |                 data_holder self = data_var{data_t<tp>()};
Что тут делать?

thunar ★★★★★
() автор топика
Ответ на: комментарий от thunar

А конструктор data_holder от нужного data_var прописан?
Может наличие другого консруктора data_holder автоматически отменяет стандартный конструктор от базового типа, если он там есть вообще? Попробуй прописать data_holder(data_t) : data_var(...)

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от mittorn

Ага, мне походу нужен мультивариантный визитор [https://www.cppstories.com/2018/09/visit-variants/]. Т.е. я до тела констуктора инстанцирую холдер базовым классом, а в теле уже настраиваю мультивариантным визитором...

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)

Есть, как минимум, две вещи, которые вы делаете не правильно.

Во-первых, вы не приводите минимального примера, демонстрирующего что вы пытаетесь сделать и с какими проблемами сталкиваетесь. Чтобы было видно какие ошибки выдает вам компилятор и который можно было бы модифицировать.

Без этого сложно понять что вы хотите, в чем именно у вас проблема и как именно вам можно помочь.

Во-вторых, у вас:

struct config_holder : config_var {

что, по сути, разворачивается в:

struct config_holder : std::variant<config_t<1>, config_t<2>, ...> { ... };

Зачем кому-то может потребоваться публичное наследование от std::variant понять сложно. С++, конечно, позволяет стрелять по ногам и себе, и тем, кто затем будет сопровождать ваш код, множеством способов, но не обязательно же выбирать самые изощренные из них.

Что наводит на мысли о том, что вы вообще пытаетесь найти решение не той проблемы.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Да, мне надо наследоваться от варианта, т.к. эти классы потом отправляются в визиторы, которые биндят к ним уже соответствующие функции. Т.е. снаружи функция принимает холдер варианта, а возвращает его биндинг к конкретной функции из числодробильного бэкэнда. Функции, естественно, зависят от параметров шаблона. У меня этих классов много, но интерфейс к ним должен быть один, потому так и сделал. Не вижу как тут проще поступить.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)
Ответ на: комментарий от thunar

Да, мне надо наследоваться от варианта, т.к. эти классы потом отправляются в визиторы, которые биндят к ним уже соответствующие функции.

А наследоваться-то зачем? Почему не иметь просто экземпляры-варианты + функции-фабрики для них?

Применяя наследование вы создаете себе проблемы, вроде той, что у вас есть параметр конструктора, на основании которого нужно как-то сконструировать базовый класс.

Не вижу как тут проще поступить.

Никто, кроме вас, пока не видит постановки задачи.

eao197 ★★★★★
()
Ответ на: комментарий от thunar

Ага, получилось как задумал [https://godbolt.org/z/3Wrn99K4a] Осталось перенести на реальный код.

eao197, дык дай пример как это по-человечески сделать, что бы было компактней и понятней. Я, пока, не соображу, не погромист же...

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)

Например, есть базовый шаблонный класс в котором содеражатся настройки:

Есть класс, содержащий данные, который дожен инициироваться с помощью config_t:

В чем проблема-то?

data_t::data_t(config_t<T>&);

zx_gamer ★★★
()
Последнее исправление: zx_gamer (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Не, data_t ничего не знает о config_t. Это тривиальная структура, что уходит в бэкэнд. Вся инициализация и управления памяью уже в классе потомке, который доступен юзеру. Я фактически делаю расчётные блоки, из которых уже извне собирается алгоритм.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)
Ответ на: комментарий от thunar

eao197, дык дай пример как это по-человечески сделать, что бы было компактней и понятней. Я, пока, не соображу, не погромист же…

Моя реакция в первую очередь о том, как бы я повел себя, если бы мне достался такой код на сопровождение.

По поводу того, как сделать, вынужден повторить: вы не озвучили исходную задачу.

Вместо этого вы пришли с костылями и сказали, мол, у меня почему-то костыли не костыляют, не пойму как починить.

Ну вот хз как их починить.

Но есть ощущение, что костыли вам вообще не нужны.

В частности, вот у вас есть config_var. Вполне себе понятный variant. Непонятно, зачем из него делать еще и config_holder. Ведь, как мне пока видится, задача config_holder-а в том, чтобы в конструкторе понять какой именно config_var создать.

Ну так почему бы и не сделать так:

using config_var = std::variant<...>;

[[nodiscard]] config_var make_config(int rd) {
  switch(rd) {
    case 1: return config_t<1>{...};
    case 2: return config_t<2>{...};
    ...
  }
  throw std::runtime_error{ "make_config: invalid rd" };
}

Простая функция-фабрика, без всяких фокусов с конструкторами и наследованием.

Про связь между config_holder и data_t вообще непонятно.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

зачем из него делать еще и config_holder

Так я же писал:

варианты хранятся в холдере где для них делается выделение памяти, инициализация и пр

Т.е. это верхний слой, который осуществляет RAII и единственный доступный со стороны пользователя (экспортируется в python). Конкретный хранимый им вариант зависит от размерности аргуммента config_t, что передаётся в config_holder'е. Тот вариант, что data_holder в себе хранит потом биндится к функциям из бекэнда (которых over 9000 в зависимости от типа численной схемы). Я, поначалу, думал сделать это через композицию, но тогда, получается, нужно дописывать ещё и conversion method. Т.е. ещё более громоздко получается.

То что я написал, вроде, работает. Мне главное понять, всё ли там синтаксически и семантически корректно и нет ли UB. Если это корректно, то наверное я просто напишу подробное пояснение — всё равно я ко всем исходникам пишу описание в LaTeX'е...

Простая функция-фабрика, без всяких фокусов с конструкторами и наследованием.

Ну вот тут возвращаются голые классы, а мне нужно упакованные в *_holder'ы.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 9)
Ответ на: комментарий от thunar

варианты хранятся в холдере где для них делается выделение памяти, инициализация и пр

Если вы про это:

struct config_holder : config_var {...};

То никакие варианты внутри вашего config_holder-а не хранятся. Этот ваш config_holder и есть тот самый вариант, только с лишними наворотами и мудреным конструктором. И память для config_holder-а вместе с инициализацией происходит точно так же, как и в случае прямого использования голого config_var.

Я бы мог увидеть пользу от типа config_holder, если бы вам нужно было что-то вроде strong typedef. Но что-то вы об этом ни слова.

В общем, картина мира есть только в вашей голове, делиться ей с участниками обсуждения вы не хотите, ну и ваше дело.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И память для config_holder-а вместе с инициализацией происходит точно так же, как и в случае прямого использования голого config_var.

Ну, например, там внутри ещё харнятся unique_ptr для ресурсов, дополнительные методы и пр.. Я не стал их все указывать что бы не загромождать минимальный пример.

В общем, картина мира есть только в вашей голове, делиться ей с участниками обсуждения вы не хотите, ну и ваше дело.

Вот я попытался изобразить схему как это должно работать:

                                            <- config_t<1> 
python <- config_holder(RAII, arg0: n, ...) <- config_t<2>
                                            <- config_t<3>
                              
                                                      <- data_t<float>
python <- data_holder(RAII, arg0: config_holder, ...) <- data_t<double>
                                                      <- data_t<int>

python -> func(arg0: config_holder, arg1: data_holder, ...) -> call func_t<nd, tp> -> call backend func instance
Вот эти func, *_holder'ы биндятся в пайтон и только они доступны юзеру. Из них уже как из конструктора собирается расчётная задача.

Знание о том что это экспортируется в python не обязательно, достаточно того что наружу торчат статично-полиморфные классы.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 8)
Ответ на: комментарий от thunar

Ну, например, там внутри ещё харнятся unique_ptr для ресурсов, дополнительные методы и пр.. Я не стал их все указывать что бы не загромождать минимальный пример.

Ну ни хера себе умолчания, пардон май френч.

Понятнее сильно не стало. Ваши схемы отображают ваши же костыли, а не то, что вам нужно получить.

Но если вам нужны именно holder-ы, то непонятно что не позволяет вам делать так:

using config_var = std::variant<...>;

class config_holder
{
  config_var _config;
  ... // Еще что-то.

  [[nodiscard]] static config_var
  make_config(int nd) {...}

public:
  config_holder(int nd) : _config{make_config(nd)} {...}
};

Наружу из config_holder-а ваш конфиг можно выставить как обычным методом-getter-ом, так и (если уж очень хочется задействовать больше фишек из C++) оператором приведения к типу:

class config_holder {
  ...
public:
  operator const config_var&() const noexcept {
    return _config;
  }
  ...
};

и тогда вы сможете передавать свой config_holder везде, где ожидается const config_var&.

Но простой метод-геттер будет проще и принесет меньше сюрпризов.

eao197 ★★★★★
()
Ответ на: комментарий от mittorn

Приходится — слишком много дублирующихся функций и классов развелось, отличающихся только размерностью задачи или типом данных. А документация к ним одна и так же и это всё дублируется. Нехорошо, не должно такого быть, только с толку сбивает.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 3)
Ответ на: комментарий от eao197

Ага, ну это второй способ, через композицию получается. В конструкторах всё равно визиторы будут, и добавляется ещё и геттер. С другой стороны, вроде стилистически это как-то понятней...

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 2)