我有一个通用的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];
}
};
有什么想法吗?
答案 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)
我喜欢基于overload
和make_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];
}
});