具有类型特征的通用lambda

时间:2018-04-26 16:19:50

标签: c++ templates lambda c++14

我有一个通用的lambda:

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 :(得分:3)

在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-overloaded-lambda-with-variadic-template-and-variable-capture

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];
        }
    });

答案 1 :(得分:0)

假设您知道每个对象都会在内部处理其绘图,您可以执行类似......

的操作
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则根据constexpr bool返回适当的类型。

答案 2 :(得分:0)

如果我们陷入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)...);
}

然后你的身体只是两个通用的lambdas:

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); 
}

随意重新组织适合您用例的参数。

答案 3 :(得分:0)

我喜欢基于overloadmake_overload的Jarod42的C ++ 14解决方案但是(Jarod:如果我错了,请纠正我)一个缺点:调用operator()的如果在继承的类中有一个且只有一个 overload可用,则operator()对象有效。

所以你必须传递第二个lambda(通用的),如下所示

[&](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时启用它,除非避免与第一个lambda“碰撞”,否则没有其他原因禁用它。

我认为最好允许使用一组特定的参数启用多个lambda,并将其称为第一个可用的。

所以我建议递归lambda_overload

template <typename...>
struct lambda_overload;

地面案例与Jarod42地面overload基本相同,并使用最后一个lambda的operator()(没有跟随“碰撞”的风险)

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

   using L::operator();
 };

但在递归版本中有点不同。

定义调用operator()的模板func(),将0(一个int)作为第一个参数传递并转发收到的其他参数

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

首选func()(第一个参数是int)是SFINAE启用的,如果(且仅当)第一个lambda接受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永远定义,并在{{1}的以下递归级别调用operator() }}

lambda_overload

这样就不存在“碰撞”的风险,因为如果有超过 template <typename ... As> auto func (long, As && ... as) { return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); } 的可用,它就会执行第一个可用的。

因此可以按如下方式调用operator()

make_lambda_overload()

避免第二个通用lambda的SFINAE禁用部分。

以下是完整(但简化)的示例

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];
    }
});