我喜欢基于 overload 和 make_overload 的 Jarod42 的 C++14 解决方案,但有(Jarod:如果我错了,请纠正我)一个缺点:调用 overload 对象的 operator() 在以下情况下有效一个并且只有一个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)...); }
当(且仅当)第一个 lambda 接受 operator() 的给定参数列表时,才启用首选 func()(第一个参数是 int)
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)永远定义并在lambda_overload的以下递归级别中调用operator()
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];
}
});
避免第二个通用 lambda 的 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()="
}