2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу 1, 2  След.
 
 Тип dependent sum в C++
Сообщение22.10.2019, 00:54 
Заслуженный участник


31/12/15
936
Нужно создать структуру или класс, где хранить информацию о геометрических фигурах. Должен быть указан тип фигуры (плоскость, прямая, точка) и параметры. Но для плоскости одно число и тип параметров, для прямой другое, а для точки третье. Это то, что в функциональных языках называется dependent sum type. Можно ли это сделать в C++?

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 01:02 
Заслуженный участник


09/05/12
25179
Раньше - union, теперь - std::variant.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 01:16 


07/08/16
328
george66, я предложу каноничный способ решения проблемы: делаете базовый чисто абстрактный класс $Figure$, который будет предоставлять лишь интерфейс для реализации в классах, от него пронаследованных, далее определяете конкретные классы $Line$, $Point$ и так далее, которые собственно, этот интерфейс реализуют.
Когда будет нужно создать экземляры типа - держите указатель (лучше умный) на $std::vector<std::unique\_ptr<Figure>>$ (можно сначала и без умных указателей, но лучше потом перейти на них), а создаёте соответственно конкретные экземпляры типа $Line$, для каждого такого типа у нас уже есть конструктор с нужным количеством параметров.
Это так называемый динамический полиморфизм, задействующий механизм виртуальных функций.
Нужен ли пример кода или же Вам это решение по каким либо причинам не подходит?

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 02:33 
Заслуженный участник


31/12/15
936
Pphantom в сообщении #1421929 писал(а):
Раньше - union, теперь - std::variant.

Спасибо.

-- 22.10.2019, 02:40 --

Sdy в сообщении #1421930 писал(а):
george66, я предложу каноничный способ решения проблемы: делаете базовый чисто абстрактный класс $Figure$, который будет предоставлять лишь интерфейс для реализации в классах, от него пронаследованных, далее определяете конкретные классы $Line$, $Point$ и так далее, которые собственно, этот интерфейс реализуют.
Когда будет нужно создать экземляры типа - держите указатель (лучше умный) на $std::vector<std::unique\_ptr<Figure>>$ (можно сначала и без умных указателей, но лучше потом перейти на них), а создаёте соответственно конкретные экземпляры типа $Line$, для каждого такого типа у нас уже есть конструктор с нужным количеством параметров.
Это так называемый динамический полиморфизм, задействующий механизм виртуальных функций.
Нужен ли пример кода или же Вам это решение по каким либо причинам не подходит?

Я хочу хранить список всех фигур (всех типов). Если это возможно при таком подходе, конечно, дайте пример.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 02:50 


27/08/16
10218
Pphantom в сообщении #1421929 писал(а):
Раньше - union, теперь - std::variant.
Вредный совет для человека, плохо знающего плюсы. Если он про union не знал - значит, ему он и не был нужен.

george66 в сообщении #1421928 писал(а):
Должен быть указан тип фигуры (плоскость, прямая, точка) и параметры. Но для плоскости одно число и тип параметров, для прямой другое, а для точки третье.
Запихните все поля в один класс. Используйте только релевантные. Я не шучу. Архитектурно будет криво, но зато не будет требоваться квалификация в С++. В плюсах нельзя нормально проектировать классы, не представляя, где будут аллокироваться объекты, как их будут создавать и где использовать.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 02:57 
Заслуженный участник


31/12/15
936
В принципе, нетрудно хранить фигуры в трёх разных списках (плоскости, точки, прямые) и всего остального тоже сделать три (рисовальных буферов для OpenGL). Я надеялся, есть простое решение.

-- 22.10.2019, 02:59 --

realeugene в сообщении #1421936 писал(а):
Запихните все поля в один класс.

Я пока так и делаю.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 02:59 


27/08/16
10218
george66 в сообщении #1421937 писал(а):
Я надеялся, есть простое решение.

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

"Один список" может быть массивом, хранящим сами обьъекты, может быть массивом, хранящим указатели на объекты, аллокированные на куче, может быть одно- или двусвязный список и так далее. От этого зависит, как реализовывать ваш полиморфный объект. А по списку на тип может быть максимально производительно.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 03:16 
Заслуженный участник


31/12/15
936
Pphantom в сообщении #1421929 писал(а):
Раньше - union, теперь - std::variant.

А какие типы можно запихнуть в std::variant? Например, удастся ли туда запихнуть типы из Qt?

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 07:16 
Заслуженный участник


28/04/09
1933
george66 в сообщении #1421939 писал(а):
Например, удастся ли туда запихнуть типы из Qt?
Да, удастся.

Пример использования std::variant:
код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <iostream>
#include <variant>
#include <vector>
#include <type_traits>

template <typename>
constexpr auto always_false_v = false;

// in std:: since C++20
template <typename Type>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<Type>>;

using Real = double;

struct XYZ
{
    Real x;
    Real y;
    Real z;
};

using Point = XYZ;

using Vector = XYZ;

struct Line
{
    Point point;
    Vector direction;
};

struct Plane
{
    Point point;
    Vector normal;
};

using GeometricObject = std::variant<Point, Line, Plane>;

using GeometricObjects = std::vector<GeometricObject>;

std::ostream& operator <<(std::ostream& outputStream, const XYZ& xyz)
{
    outputStream << "(" << xyz.x << ", " << xyz.y << ", " << xyz.z << ")";
    return outputStream;
}

void print(const Point& point)
{
    std::cout << "point: " << point << std::endl;
}

void print(const Line& line)
{
    std::cout << "line: " << std::endl;
    std::cout << "\t";
    print(line.point);
    std::cout << "\tdirection: " << line.direction << std::endl;
}

void print(const Plane& plane)
{
    std::cout << "plane: " << std::endl;
    std::cout << "\t";
    print(plane.point);
    std::cout << "\tnormal: " << plane.normal << std::endl;
}

void print(const GeometricObjects& geometricObjects)
{
    std::size_t index{};
    for (const auto& geometricObject : geometricObjects)
    {
        ++index;
        std::cout << index << ") ";
        std::visit([](const auto& geometricObject){print(geometricObject);}, geometricObject);
    }
}

void reflect_xyz(XYZ& xyz)
{
    xyz.x = -xyz.x;
}

constexpr auto reflect_object = [] (auto& geometricObject)
{
    using SomeGeometricObject = remove_cvref_t<decltype(geometricObject)>;
    if constexpr (std::is_same_v<SomeGeometricObject, Point>)
    {
        reflect_xyz(geometricObject);
    }
    else if constexpr (std::is_same_v<SomeGeometricObject, Line>)
    {
        reflect_xyz(geometricObject.point);
        reflect_xyz(geometricObject.direction);
    }
    else if constexpr (std::is_same_v<SomeGeometricObject, Plane>)
    {
        reflect_xyz(geometricObject.point);
        reflect_xyz(geometricObject.normal);
    }
    else
    {
        static_assert(always_false_v<SomeGeometricObject>, "Unknown geometric object");
    }
};

void reflect(GeometricObjects& geometricObjects)
{
    for (auto& geometricObject : geometricObjects)
    {
        std::visit(reflect_object, geometricObject);
    }
}

int main()
{
    const Point point1{1.0, 0.0, 2.0};
    const Point point2{2.0, 1.0, 3.0};
    const Line line{Point{-2.0, 1.0, 5.0}, Vector{1.0, 1.0, 1.0}};
    const Plane plane{Point{8.0, 5.0, -2.0}, Vector{0.0, 1.0, 0.0}};
    GeometricObjects geometricObjects{point1, line, point2, plane};
    std::cout << "Before reflection:" << std::endl;
    print(geometricObjects);
    std::cout << std::endl;
    reflect(geometricObjects);
    std::cout << "After reflection:" << std::endl;
    print(geometricObjects);
}

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 09:44 
Заслуженный участник


09/05/12
25179
realeugene в сообщении #1421936 писал(а):
Вредный совет для человека, плохо знающего плюсы. Если он про union не знал - значит, ему он и не был нужен.
Я же написал, что так было раньше. В конце концов, человек может просто встретить при поисках подобный код, да и шансы на работу именно с C++17 пока еще не стопроцентные (а boost - это по меньшей мере формально не совсем язык).
george66 в сообщении #1421939 писал(а):
А какие типы можно запихнуть в std::variant? Например, удастся ли туда запихнуть типы из Qt?
Любые. Соответственно, удастся.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 10:59 


07/08/16
328
george66 в сообщении #1421935 писал(а):
Я хочу хранить список всех фигур (всех типов). Если это возможно при таком подходе, конечно, дайте пример.

Этот подход в том и заключается, что вы храните вектор указателей на экземпляры базового типа, а указатель на базовый класс умеет смотреть на типы производные.
Но я ниже вижу Ваше сообщение, что это всё происходит в $Qt$, с использованием $Qt$-шных типов. Значит какая необходимость вообще городить велосипед?
Вот пример из документации:
Код:
switch (shape) {
            case Line:
                painter.drawLine(rect.bottomLeft(), rect.topRight());
                break;
            case Points:
                painter.drawPoints(points, 4);
                break;
            case Polyline:
                painter.drawPolyline(points, 4);
                break;
            case Polygon:
                painter.drawPolygon(points, 4);
                break;
            case Rect:
                painter.drawRect(rect);
                break;
            case RoundedRect:
                painter.drawRoundedRect(rect, 25, 25, Qt::RelativeSize);
                break;
            case Ellipse:
                painter.drawEllipse(rect);
                break;
            case Arc:
                painter.drawArc(rect, startAngle, arcLength);
                break;
            case Chord:
                painter.drawChord(rect, startAngle, arcLength);
                break;
            case Pie:
                painter.drawPie(rect, startAngle, arcLength);
                break;
            case Path:
                painter.drawPath(path);
                break;
            case Text:
                painter.drawText(rect,
                                 Qt::AlignCenter,
                                 tr("Qt by\nThe Qt Company"));
                break;
            case Pixmap:
                painter.drawPixmap(10, 10, pixmap);
            }

Используют они экземпляр типа $QPainter$, вот по нему документация - https://doc.qt.io/qt-5/qpainter.html
Вот та страница документации, откуда я взял этот пример - https://doc.qt.io/qt-5/qtwidgets-painti ... ample.html

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 14:16 
Заслуженный участник


27/04/09
28128
george66 в сообщении #1421935 писал(а):
Я хочу хранить список всех фигур (всех типов). Если это возможно при таком подходе, конечно, дайте пример.
А, ну такое разумеется возможно в любом языке с мало-мальской поддержкой ООП, только вам придётся держать в списке указатели. Чтобы можно было держать там сами фигуры, по-моему упомянутый Pphantom std::variant должен быть в самый раз. Я не собираюсь писать на современных плюсах, но выглядит не так уж страшно. union хуже тем, что достался в наследство от C и не имеет никаких typesafe штук: можно обращаться к любому полю любой альтернативы, и даже чтобы просто знать, какая альтернатива хранится, придётся засовывать union внутрь структуры (или класса), имеющей дополнительное поле-селектор. И мы можем не уследить и всё равно обратиться к полю не той альтернативы, потому что соответствие обращений значению селектора никак проверяться ни статически, ни в рантайме не будет.

realeugene в сообщении #1421936 писал(а):
Вредный совет для человека, плохо знающего плюсы. Если он про union не знал - значит, ему он и не был нужен.
Ну а теперь нужен. Волков бояться — в лес не ходить. Я вообще не понимаю, зачем писать на плюсах, если не планировать использовать современное. Есть C, есть Rust и т. д..

realeugene в сообщении #1421936 писал(а):
Используйте только релевантные. Я не шучу. Архитектурно будет криво, но зато не будет требоваться квалификация в С++.
Какая нужна квалификация, чтобы осилить union?? А новый вариант должен быть ещё лучше, нам обязательно надо будет объявить по типу для конкретных фигур, и они нам могут пригодиться дальше и как отдельные, и в виде объединений, и объединения можно будет делать из разного числа типов фигур.

Pphantom в сообщении #1421955 писал(а):
Любые.
А cppreference пишет, что не совсем любые:
    As with unions, if a variant holds a value of some object type T, the object representation of T is allocated directly within the object representation of the variant itself. Variant is not allowed to allocate additional (dynamic) memory.

    A variant is not permitted to hold references, arrays, or the type void.

Sdy в сообщении #1421957 писал(а):
Используют они экземпляр типа $QPainter$, вот по нему документация - https://doc.qt.io/qt-5/qpainter.html
ТС рисует напрямую через OpenGL. Кстати, пожалуйста используйте tt для кода внутри текста или syntax (или code, когда syntax не знает язык) для кода блоком, в виде формул код смотрится ещё страшнее чем без форматирования. :D

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 14:42 
Заслуженный участник


31/12/15
936
EtCetera в сообщении #1421943 писал(а):
george66 в сообщении #1421939 писал(а):
Например, удастся ли туда запихнуть типы из Qt?
Да, удастся.

Пример использования std::variant:
код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <iostream>
#include <variant>
#include <vector>
#include <type_traits>

template <typename>
constexpr auto always_false_v = false;

// in std:: since C++20
template <typename Type>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<Type>>;

using Real = double;

struct XYZ
{
    Real x;
    Real y;
    Real z;
};

using Point = XYZ;

using Vector = XYZ;

struct Line
{
    Point point;
    Vector direction;
};

struct Plane
{
    Point point;
    Vector normal;
};

using GeometricObject = std::variant<Point, Line, Plane>;

using GeometricObjects = std::vector<GeometricObject>;

std::ostream& operator <<(std::ostream& outputStream, const XYZ& xyz)
{
    outputStream << "(" << xyz.x << ", " << xyz.y << ", " << xyz.z << ")";
    return outputStream;
}

void print(const Point& point)
{
    std::cout << "point: " << point << std::endl;
}

void print(const Line& line)
{
    std::cout << "line: " << std::endl;
    std::cout << "\t";
    print(line.point);
    std::cout << "\tdirection: " << line.direction << std::endl;
}

void print(const Plane& plane)
{
    std::cout << "plane: " << std::endl;
    std::cout << "\t";
    print(plane.point);
    std::cout << "\tnormal: " << plane.normal << std::endl;
}

void print(const GeometricObjects& geometricObjects)
{
    std::size_t index{};
    for (const auto& geometricObject : geometricObjects)
    {
        ++index;
        std::cout << index << ") ";
        std::visit([](const auto& geometricObject){print(geometricObject);}, geometricObject);
    }
}

void reflect_xyz(XYZ& xyz)
{
    xyz.x = -xyz.x;
}

constexpr auto reflect_object = [] (auto& geometricObject)
{
    using SomeGeometricObject = remove_cvref_t<decltype(geometricObject)>;
    if constexpr (std::is_same_v<SomeGeometricObject, Point>)
    {
        reflect_xyz(geometricObject);
    }
    else if constexpr (std::is_same_v<SomeGeometricObject, Line>)
    {
        reflect_xyz(geometricObject.point);
        reflect_xyz(geometricObject.direction);
    }
    else if constexpr (std::is_same_v<SomeGeometricObject, Plane>)
    {
        reflect_xyz(geometricObject.point);
        reflect_xyz(geometricObject.normal);
    }
    else
    {
        static_assert(always_false_v<SomeGeometricObject>, "Unknown geometric object");
    }
};

void reflect(GeometricObjects& geometricObjects)
{
    for (auto& geometricObject : geometricObjects)
    {
        std::visit(reflect_object, geometricObject);
    }
}

int main()
{
    const Point point1{1.0, 0.0, 2.0};
    const Point point2{2.0, 1.0, 3.0};
    const Line line{Point{-2.0, 1.0, 5.0}, Vector{1.0, 1.0, 1.0}};
    const Plane plane{Point{8.0, 5.0, -2.0}, Vector{0.0, 1.0, 0.0}};
    GeometricObjects geometricObjects{point1, line, point2, plane};
    std::cout << "Before reflection:" << std::endl;
    print(geometricObjects);
    std::cout << std::endl;
    reflect(geometricObjects);
    std::cout << "After reflection:" << std::endl;
    print(geometricObjects);
}

Спасибо.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 14:45 


27/08/16
10218
arseniiv в сообщении #1421967 писал(а):
Какая нужна квалификация, чтобы осилить union
В плюсах - большая. Чтобы писать код с ними в сишном стиле. А главное, что нужно понимать, что union - это только средство оптимизации кода по требуемой памяти. Т. е. до оптимизации по памяти его можно безболезненно заменить на struct из альтернатив.

 Профиль  
                  
 
 Re: Тип dependent sum в C++
Сообщение22.10.2019, 15:27 


07/08/16
328
george66 в сообщении #1421935 писал(а):
Я хочу хранить список всех фигур (всех типов). Если это возможно при таком подходе, конечно, дайте пример.

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

код: [ скачать ] [ спрятать ]
Используется синтаксис C++
#include <vector>
#include <ctime>
#include <memory>
#include <iostream>


//Абстрактный класс, предоставляющий интерфейс для реализации классами конкретными
class Shape
{
    //некоторые общие характеристики фигур можно объявить здесь
protected:
   size_t centerOfGravity;
public :
   virtual void Draw() = 0;//две чисто виртуальные функции, которые будут обязаны перегрузить
   //классы, наследуемые от Shape
   virtual void Rotate() = 0;
};

//Объявление и определение первого конкретного класса, он обязан перегрузить
//чисто виртуальные функции
class Triangle : public Shape
{
public:
    Triangle(size_t firstSide=3, size_t secondSide=4, size_t thirdSide=5)
    {
        //do sth
    }

   void Draw()
   {
       std::cout<<"drawing of triangle"<<std::endl;
   }


   void Rotate()
   {
       std::cout<<"rotating of triangle"<< std::endl;
   }

};

//Объявление и определение второго конкретного класса
class Circle : public Shape
{
public:
    Circle(size_t radius=10, size_t center = 0)
    {
        centerOfGravity = center;
    }
   void Draw()
   {
       std::cout<<"drawing of circle"<< std::endl;
   }

   void Rotate()
   {
       std::cout<<"rotating of circle"<<std::endl;
   }
};


int main()
{
    std::srand(time(nullptr));
    //создаем вектор умных указателей на объекты типа Shape
   std::vector<std::unique_ptr<Shape>> shapes;
   size_t size = 20;
   for(size_t index = 0; index < size; index++)
   {
       if(rand()%100 > 50)
       {
           //Выделяем память под Circle, создаем умный указатель, который будет на эту память
           //смотреть и вставляем в вектор
           shapes.push_back(std::unique_ptr<Shape>(new Circle()));
       }
       else
       {
           //Тоже для треугольников
           shapes.push_back(std::unique_ptr<Shape>(new Triangle()));
       }
   }

   //Проходим по нашей коллекции указателей.
   //Засчет механизма вызова виртуальных функций, для каждого
   //из объектов будут вызваны соответствующие процедуры поворота и рисования.
   for(const auto& shape : shapes)
   {
       shape->Draw();
       shape->Rotate();
   }
}

 


-- 22.10.2019, 20:29 --

arseniiv в сообщении #1421967 писал(а):
ТС рисует напрямую через OpenGL.

Тогда мой совет с QPainter для него бесполезен.
arseniiv в сообщении #1421967 писал(а):
стати, пожалуйста используйте tt для кода внутри текста или syntax (или code, когда syntax не знает язык) для кода блоком, в виде формул код смотрится ещё страшнее чем без форматирования. :D

Спасибо, впредь так и буду делать.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 22 ]  На страницу 1, 2  След.

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group