Как стать автором
Обновить
52
0
Алексей Егоров @PkXwmpgN

Пользователь

Отправить сообщение

С лямбдами будет скорее всего все как у вас, просто кортеж переедет в capture.


Типа токого


template<typename ...As>
constexpr decltype(auto) part(As && ...as)
{   
    return [as = std::make_tuple(std::forward<As>(as)...)](auto && ...as2)
    {
        return apply(invoke, std::tuple_cat(forward_tuple(as), 
                                            std::forward_as_tuple(as2...)));
    };
}

или такого


template<typename F, typename ...As>
constexpr decltype(auto) part(F && f, As && ...as)
{   
    return [f = std::forward<F>(f), 
            a = std::make_tuple(std::forward<As>(as)...)](auto&&... as2)
    {
        return apply(f, std::tuple_cat(forward_tuple(a), 
                                       std::forward_as_tuple(as2...)));
    };
}

У вас вроде понагляднее.


Кстате, следует упомянуть что forward_as_tuple и invoke у вас — это дополнительные функциональные объекты, обертки над стандартными функциями. Стандартную функцию в apply таким образом не запихать.

template <typename ... As>
constexpr auto part (As && ... as)

Почему нельзя использовать "стандартную" сигнатуру?


template<typename F, typename ... As >
constexpr auto part (F && f, As &&... as )

Это позволит проконтролировать, что первым аргументом должен идти объект, над которым будет выполненно преобразование. А если сохранить объект отдельно от кортежа, то можно вызывать просто apply. Стандартные apply и invoke оба обрабатывают callable-объекты, и скорее всего apply будет реализован через invoke.

Uniforms (будем называть их формами)

Мне кажется это очень странное, вводящие в заблужение название.
Здесь ближе значение единый, одинаковый.


Квалификатор uniform подчеркивает, что значение переменной будет одинаковым в процессе обработки всего примитива. В отличии от переменной с квалификатором attribute, значение которой засисит от обрабатываемой вершины или переменной с квалификатором varying, значение которой может меняться (интерполироваться).

Также, на мой взгляд, никаким улучшением времени компеляции, путь даже она будет происходит мгновенно при любом изменение, нельзя оправдать увеличение потребления памяти в рантайме с учетом того, что производительность останется таже. Вы же этот продукт в конечном итоге отдатите пользователям. Как минимум, все подобные манипуляции должы производиться только под дебагом. И здесь pimpl опять лучше, потому что не меняет интерфейс класса. Если я без скрытой реализации писал a.result_by_module(4), то и со скрытой реализацией я буду писать а.result_by_module(4).

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


И потом, из стать несовсем понятно, чем ваш пример лучше pimpl? Вы привели единственный аргумент


необходимость писать обертку для всех методов класса примерно таким образом (опустим дополнительные сложности по управлению памятью):

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


По поводу памяти, можно также использовать unique_ptr для хранения указателя на реализацию. Плюс ко всему здесь есть улучшение под название The Fast Pimpl Idiom.


Пример
// A.h

class A 
{
public:
    A();
    ~A();

    void next_step();
    int result_by_module(int);

private:
    struct impl;
    std::unique_ptr<impl> _impl;
};

// A.cpp

struct A::impl
{
    int counter_ = 0;
};

A::A()
    : _impl(std::make_unique<impl>())
{}

A::~A() = default;

void A::next_step() 
{
    ++_impl->_counter;
}

int A::result_by_module(int m) 
{
    return _impl->_counter % m;
}

Еще ваша реализация полность убирает понятие константности из интерфейса.

Объект класса А занимает 4 байта (поле типа int), независимо от сложности предоставляемого интерфейса.


В вашей реализации объект класса А занимает:
16 байт * 2 (количество_методов в интерфейсе) +
4 байта для указателя на объект (unique_ptr) +
4 байта A_context +
8 байт для указателя на контекст (shader_ptr) +
8 байт объект bind +
8 байт для указатель на контект в bind'е +
8 байт для указатель на контект в лямбде.


А если в интерфейсе 10 методов?

А если у меня иерархия не линейная?


Скрытый текст
struct entity {};
struct geometry : entity {};
struct model : entity {};
Но как пишет Александреску — это самый неудачный паттерн, потому редко используемый

Это немного упрошенный пример, у Александреску более аккуратный, обобщенный шаблон (в Loki, dynamic_cast вынемен отдельно), но суть там такая же. Не такой уж он и неудачный.


Намой взгляд, возможности по расшерению и поддержке этой модели, такие же как и у Cyclic Visitor. Только вместо виртуальной функции мне нужно добавить поле в enum, вы же понимаете, что если я добавлю поле в enum все существующие visitor'ы нужно будет перекомпелировать. Что если это библиотека и у меня нет возможности добавить поле в enum, также как у меня может не быть возможности добавить виктуальную функцию в случаи с Cyclic Visitor и перекомпилировать все?

У obj нет метода visit. Если бы был, тогда нужна перегрузка для всех типов — это циклическая зависимость.
Про этот пример можно подробно прочитать в книге Andrei Alexandrescu. Modern c++ design.

мы выберем троих специальным методом произвольных чисел и вручим подарки

Ознакомьтесь со значением слова конкурс.

Про Новосибирск поддержу. Жил некоторое время в Академгородке — это потрясающее место, как-будто попадаешь в другой мир. По работе там большую часть одеяла на себя стягивает 2gis, мне кажется.

Ну, вроде Ростов обсудили, uTeam вроде тоже — все ищем Риту!

джуны чего — то уже не хочется

Зато посмотрите как Рита кодит.

Из всех перечисленных аргументов, самым серьезным оказалась Рита.

Еще был вопрос в комментариях (я его отклонил, случайно), "почему такие засветы на потолке?"
Это потому что нет тененй пока.


вот так будет с тенями

Нет, посмотрите внимательно, там указано вот так


<цвет_пикселя> = <фоновой (ambient)> * Ka + (<рассеянной (diffuse) источник 1> * Kd + <блики (specular) источник 1> * Ka + ... + <рассеянной (diffuse) источник n> * Kd + <блики (specular) источник n> * Ka)

где Ka, Kd, Ks — это свойства метериала отражать соответствующий свет — т.е. то что у вас в цветовом буфере.

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


<цвет_пикселя> = <изначальный_цвет> + <цвет_источника_1> + ... + <цвет_источника_n>

А должно быть так:


<цвет_пикселя> = <изначальный_цвет> * (<цвет_источника_1> + ... + <цвет_источника_n>)

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


<цвет_пикселя> = <изначальный_цвет> * <цвет_источника_1> + ... + <изначальный_цвет> * <цвет_источника_n>

Проблема в том, что у нас есть еще и зеркальная компонента, для которой нам нужно делать тоже самое, а потом сложить с диффузной компонентой. Тогда нам потребуется еще раз отрисовать все объекты и сохранить в текстуру степень зеркальности. И… как результат мы приходим к общему случаю отложеного освещения, в котором эти тектуры — нормали, альбедо и степень зеркальности создается за один проход.

Phong reflection model


Обратите внимание на формулу.
Как вы предлагаете это считать, если будите рендерить шары в буфер цвета? Ну точнее, каким образом нужно рендерить эти шары?

Ну и да, там нет окон — это дырки(отверстия) просто.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Зарегистрирован
Активность