Общая лямбда с чертами типа - PullRequest
0 голосов
/ 26 апреля 2018

У меня есть общая лямбда:

auto update = [&](auto& container, shader& effect)
{
    for (const auto& key : objects)
    {
        auto& obj = *container[key];

        if (obj.HasAnyGeometry())
        {
            m_GeometryDrawCalls.push_back({ &obj, effect });
        }
    }
};

, который обрабатывает мои 3D-объекты и добавляет их в список вызовов рисования m_GeometryDrawCalls. Все эти объекты являются производными от некоторого пользовательского класса, назовем его class Object3D. Однако недавно я добавил объект, который не является производным от Object3D, поэтому ему не нужно добавлять геометрию к m_GeometryDrawCalls, но он обрабатывает его внутренне. Я хотел бы использовать ту же функцию, чтобы справиться с этим. Это как-то возможно через шаблоны? В основном все, что мне нужно сделать для другого типа, это:

auto update = [&](auto& container, shader& effect)
{
    for (const auto& key : objects)
    {
        auto& obj = *container[key];
    }
};

Есть идеи?

Ответы [ 4 ]

0 голосов
/ 27 апреля 2018

Мне нравится решение C ++ 14 в Jarod42, основанное на overload и make_overload, но имеющее (Джарод: исправьте меня, если я ошибаюсь) недостаток: вызов operator() объекта overload работает, если один и только один operator() доступен в унаследованных классах.

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

[&](auto& container, shader& effect)
-> std::enable_if_t<!std::is_base<Object3D,
        std::decay_t<decltype(*container.begin())>>::value>
{
    for (const auto& key : objects) {
        auto& obj = *container[key];
    }
}

так что включайте его только тогда, когда отключена первая лямбда, когда нет других причин для его отключения, кроме как во избежание «столкновения» с первой лямбда.

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

Итак, я предлагаю рекурсивный lambda_overload

template <typename...>
struct lambda_overload;

с заземленным корпусом, который по существу идентичен заземлению Jarod42 overload и использует operator() последней лямбды (без риска следующих "столкновений")

template <typename L>
struct lambda_overload<L> : public L
 {
   lambda_overload (L l) : L{std::move(l)}
    { };

   using L::operator();
 };

но в рекурсивной версии это немного отличается.

Он определяет шаблон operator(), который вызывает func(), передавая 0 (a int) в качестве первого аргумента и перенаправляя другие полученные аргументы

   template <typename ... As>
   auto operator() (As && ... as)
    { return func(0, std::forward<As>(as)...); }

Предпочтительный func() (первый аргумент - int) - включен SFINAE, если (и только если) первая лямбда-функция принимает указанный список аргументов для operator()

   template <typename ... As>
   auto func (int, As && ... as)
      -> decltype( std::declval<L0>()(std::forward<As>(as)...) )
    { return L0::operator()(std::forward<As>(as)...); }

и резервная копия func() (первый аргумент long) ever определены и вызывают operator() на следующем уровне рекурсии для lambda_overload

   template <typename ... As>
   auto func (long, As && ... as)
    { return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); }

Таким образом, нет риска "столкновения", потому что если доступно более operator(), выполняется первый доступный

.

Так что make_lambda_overload() можно назвать следующим образом

auto update = make_lambda_overload(
    [&](auto& container, shader& effect)
    -> std::enable_if_t<std::is_base<Object3D,
          std::decay_t<decltype(*container.begin())>>::value>
{
    for (const auto& key : objects) {
        auto& obj = *container[key];

        if (obj.HasAnyGeometry()) {
            m_GeometryDrawCalls.push_back({ &obj, effect });
        }
    }
},
[&](auto& container, shader& effect)
{
    for (const auto& key : objects) {
        auto& obj = *container[key];
    }
});

избегая части отключения SFINAE для второй общей лямбды.

Ниже приведен полный (но упрощенный) пример

#include <iostream>

template <typename...>
struct lambda_overload;

template <typename L>
struct lambda_overload<L> : public L
 {
   lambda_overload (L l) : L{std::move(l)}
    { };

   using L::operator();
 };

template <typename L0, typename ... Ls>
struct lambda_overload<L0, Ls...> : public L0, public lambda_overload<Ls...>
 {
   lambda_overload (L0 l0, Ls ... ls)
      : L0{std::move(l0)}, lambda_overload<Ls...>{std::move(ls)...}
    { };

   // backup version (ever defined!)
   template <typename ... As>
   auto func (long, As && ... as)
    { return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); }

   // preferred version (defined only if operator() defined for L0 type)
   template <typename ... As>
   auto func (int, As && ... as)
      -> decltype( std::declval<L0>()(std::forward<As>(as)...) )
    { return L0::operator()(std::forward<As>(as)...); }

   template <typename ... As>
   auto operator() (As && ... as)
    { return func(0, std::forward<As>(as)...); }
 };

template <typename ... Ls>
auto make_lambda_overload (Ls && ... ls)
 { return lambda_overload<Ls...>{ std::forward<Ls>(ls)... }; }

int main()
 {
   auto l1 = [&](auto const & t) -> decltype((void)t.size())
    { std::cout << "-- with size() version - " << t.size() << std::endl; };

   auto l2 = [&](auto const & t)
    { std::cout << "-- generic version (also no size())" << std::endl; };

   auto lo = make_lambda_overload(std::move(l1), std::move(l2));

   lo(std::string{"0"});  // print "with size() version - 1
   lo(1);                 // print "generic version (also no size()="
 }
0 голосов
/ 26 апреля 2018

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

template<bool b>
using tf_type = std::conditional_t<b, std::true_type, std::false_type>

template<class G, class O, class E>
void add_to_if(std::true_type, G& m_GeometryDrawCalls, const O& obj, const E& effect) {
      m_GeometryDrawCalls.push_back({ &obj, effect });
}

template<class G, class O, class E>
void add_to_if(std::false_type, G& m_GeometryDrawCalls, const O& obj, const E& effect) 
{ /*do nothing*/ }

auto update = [&](auto& container, shader& effect)
{
for (const auto& key : objects)
{
    auto& obj = *container[key];

         //obj.HasAnyGeometry() must return a constexpr bool
        tf_type<obj.HasAnyGeometry()> TF;
        add_to_if(TF, m_GeometryDrawCalls, obj, effect);
    }
}
};

Add_to будет специализироваться для std::true_type/std::false_type, в то время как tf_type возвращает соответствующий тип, основанный на bool constexpr.

0 голосов
/ 26 апреля 2018

Если мы застряли в C ++ 14, бедняк if constexpr должен написать функцию, которая принимает две функции и просто вызывает ту, которая нужна:

template <typename True, typename False, typename... Args>
decltype(auto) static_if(std::true_type, True&& true_f, False&&, Args&&... args ) {
    return std::forward<True>(true_f)(std::forward<Args>(args)...);
}

template <typename True, typename False, typename... Args>
decltype(auto) static_if(std::false_type, True&&, False&& false_f, Args&&... args ) {
    return std::forward<False>(false_f)(std::forward<Args>(args)...);
}

И тогда ваше тело - это просто две общие лямбды:

for (const auto& key : objects)
{
    auto& obj = *container[key];
    static_if(
        // condition
        std::is_base<Object3D, std::decay_t<decltype(obj)>>{},
        // true case. NB we use e throughout, not obj
        [&](auto&& e) {
            m_GeometryDrawCalls.push_back({ &e, effect });
        },
        // false case: noop
        [&](auto&& ) {},
        obj); 
}

Не стесняйтесь реорганизовать аргументы в соответствии с вашим вариантом использования.

0 голосов
/ 26 апреля 2018

В C ++ 17 вы можете просто сделать:

auto update = [&](auto& container, shader& effect)
{
    for (const auto& key : objects) {
        auto& obj = *container[key];

        if constexpr (std::is_base<Object3D, std::decay_t<decltype(obj)>>::value) {
            if (obj.HasAnyGeometry()) {
                m_GeometryDrawCalls.push_back({ &obj, effect });
            }
        }
    }
};

Для C ++ 11 вы можете использовать struct overloaded и SFINAE: С c11-перегрузка-лямбда-с-вариадическим-шаблоном-и-переменной-захватом

template <class... Fs>
struct overload;

template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...>
{
    overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}

    using F0::operator();
    using overload<Frest...>::operator();
};

template <class F0>
struct overload<F0> : F0
{
    overload(F0 f0) : F0(f0) {}

    using F0::operator();
};

template <class... Fs>
auto make_overload(Fs... fs)
{
    return overload<Fs...>(fs...);
}

и затем (я использую c ++ 14 для _t):

auto update = make_overload(
    [&](auto& container, shader& effect)
    -> std::enable_if_t<std::is_base<Object3D,
                                     std::decay_t<decltype(*container.begin())>>::value>
    {
        for (const auto& key : objects) {
            auto& obj = *container[key];

            if (obj.HasAnyGeometry()) {
                m_GeometryDrawCalls.push_back({ &obj, effect });
            }
        }
    },
    [&](auto& container, shader& effect)
    -> std::enable_if_t<!std::is_base<Object3D,
                                      std::decay_t<decltype(*container.begin())>>::value>
    {
        for (const auto& key : objects) {
            auto& obj = *container[key];
        }
    });
...