Мне нравится решение 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()="
}