Reputation: 453
I have a generic 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 });
}
}
};
which processes my 3D objects and adds them to the draw call list m_GeometryDrawCalls
. All of those objects are derived from some custom class, let's call it class Object3D
. However I recently added object which is not derived from Object3D
so it does not need to add geometry to m_GeometryDrawCalls
, but it handles it internally. I would like to use same function to handle this. Is it somehow possible via templates? Basically all I need to do for other type is this:
auto update = [&](auto& container, shader& effect)
{
for (const auto& key : objects)
{
auto& obj = *container[key];
}
};
Any ideas?
Upvotes: 2
Views: 330
Reputation: 66200
I like the Jarod42's C++14 solution based over overload
and make_overload
but has (Jarod: correct me if I'm wrong) a drawback: calling a operator()
of a overload
object works if one and only one operator()
is available in inherited classes.
So you have to pass the second lambda (the generic one) as follows
[&](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];
}
}
so enabling it only when is disabled the first lambda, when there is no other reason to disable it except to avoid a "collision" with the first lambda.
I think should be preferable permit that more than one lambda is enabled with a particular set of argument and that is called the first available.
So I propose a recursive lambda_overload
template <typename...>
struct lambda_overload;
with a ground case that is substantially identically to the Jarod42 ground overload
and use the operator()
of the last lambda (no risk of following "collisions")
template <typename L>
struct lambda_overload<L> : public L
{
lambda_overload (L l) : L{std::move(l)}
{ };
using L::operator();
};
but that is a little different in the recursive version.
It define a template operator()
that call a func()
, passing 0
(a int
) as first argument and forwarding the other arguments received
template <typename ... As>
auto operator() (As && ... as)
{ return func(0, std::forward<As>(as)...); }
A preferred func()
(the first argument is an int
) is SFINAE enabled if (and only if) the first lambda accept the given argument list for an operator()
template <typename ... As>
auto func (int, As && ... as)
-> decltype( std::declval<L0>()(std::forward<As>(as)...) )
{ return L0::operator()(std::forward<As>(as)...); }
and a backup func()
(the first argument is a long
) is ever defined and call the operator()
in the following recursion level for lambda_overload
template <typename ... As>
auto func (long, As && ... as)
{ return lambda_overload<Ls...>::operator()(std::forward<As>(as)...); }
This way there isn't risk of "collision" because if more than operator()
's is available, it's executed the first one available.
So make_lambda_overload()
could be called as follows
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];
}
});
avoiding the SFINAE disable part for the second generic lambda.
The following is a full (but simplified) example
#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()="
}
Upvotes: 0
Reputation: 303057
If we're stuck in C++14, a poor man's if constexpr
is to write a function that takes two functions and just calls whichever one is desired:
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)...);
}
And then your body is just two generic 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);
}
Feel free to reorganize arguments as suits your use-case.
Upvotes: 0
Reputation: 371
Assuming that you know that each object would handle its drawing internally or not you could do something like...
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 will be specialized for std::true_type/std::false_type
while tf_type
returns the apropriate type based upon a constexpr bool.
Upvotes: 0
Reputation: 217273
In C++17, you may simply do:
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 });
}
}
}
};
For C++11, you may use struct overloaded
and SFINAE:
From 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...);
}
and then (I use c++14 for _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];
}
});
Upvotes: 3