【问题标题】:C++20 template lambdas with template parameter that is not related to function parameters带有与函数参数无关的模板参数的 C++20 模板 lambda
【发布时间】:2021-07-09 20:41:09
【问题描述】:

我有一组元组。 我正在整理它。 我可以选择在运行时使用哪个字段进行排序。

switch (columnIndex)
{
case 0:
    std::ranges::sort(rows, [reversed](const rowType& a, const rowType& b) -> bool {
        if (reversed)
        {
            return std::get<0>(a) < std::get<0>(b);
        }
        return std::get<0>(a) > std::get<0>(b);
    });
    break;
    break;
case 2:
    std::ranges::sort(rows, [reversed](const rowType& a, const rowType& b) -> bool {
        if (reversed)
        {
            return std::get<2>(a) > std::get<2>(b);
        }
        return std::get<2>(a) < std::get<2>(b);
    });
    break;
case 3:
    std::ranges::sort(rows, [reversed](const rowType& a, const rowType& b) -> bool {
        if (reversed)
        {
            return std::get<3>(a) > std::get<3>(b);
        }
        return std::get<3>(a) < std::get<3>(b);
    });
    break;
}

(不要介意缺少案例 1)

我想用 c++20 模板 lambda 替换所有代码,所以我不必重复 lambda 3 次。 我制作了一个可以用于此目的的 lambda。


using rowType = std::tuple<size_t, const char*, unsigned int, unsigned int>;
bool reversed = false; // this is just an example of input I may have
auto sortLambda = [reversed]<size_t tupleIndex>(const rowType& a,
                                                const rowType& b) -> bool {
    if (reversed)
    {
        return std::get<tupleIndex>(a) < std::get<tupleIndex>(b);
    }
    return std::get<tupleIndex>(a) > std::get<tupleIndex>(b);
};

但我无法将其传递给排序。 我发现没有任何语法允许我指定模板参数来专门化 lambda,然后将其传递给排序。 我看不到任何使用具有模板参数的 lambda 的方法,以防函数参数中未使用该模板参数。

所以我需要使用 lambda 来捕获反转标志,用函数来做这件事很麻烦。 但我还需要模板参数来选择要比较的元组索引。 如何重写该代码以使用单个 lambda(或以任何其他方式摆脱重复)?

有什么方法可以真正调用我的模板 lambda?因为我找不到允许我为 lambda 手动指定模板参数的语法,但是如果我不手动指定它,它就无法自动推导出它。

(如果我使用 Visual C++ 2019 的编译器之间存在差异)

【问题讨论】:

  • 你能发布可重现的例子吗?
  • 我更新了代码,所以模板 lambda 现在应该可以编译了。至于其他代码,示例太大,可能没有必要。

标签: c++ lambda c++20


【解决方案1】:

没有 lambda,您可以编写仿函数模板:

template <size_t index> 
struct comparator {
    bool reversed;
    bool operator()(const rowType& a, const rowType& b) const {
        if (reversed) {
            return std::get<index>(a) < std::get<index>(b);
        }
        return std::get<index>(a) > std::get<index>(b);
    }            
 };

并将其与 sort 一起使用:

std::sort(begin,end, comparator<0>{reversed});

Live Example.

请注意,当columnIndex 仅在运行时已知时,无论如何您都需要该开关。

我不认为有一个模板参数不能从参数中推导出来的 lambda 在这里实际上是有用的(好吧,见下文)。正如this answer 解释的那样,它的lamdbas operator() 是一个模板,并且通常模板参数在sort 中被推导出来,只有当operator() 被调用时。另一方面,在上述模板中,您可以在将实例传递给std::sort 之前修复模板参数,并在sort 内部调用普通的operator()


我很想知道如何仅使用仅在本地范围内定义的 lambda 来完成此操作。灵感来自this answer

#include <vector>
#include <algorithm>
#include <tuple>

using rowType = std::tuple<int,int>;

int main(){

    bool reversed = false;
    auto comparator_generator = [reversed]<size_t index>(){
        return [reversed](const rowType& a, const rowType& b){
            if (reversed) {
                return std::get<index>(a) < std::get<index>(b);
            }
            return std::get<index>(a) > std::get<index>(b);
        };
    };

    std::vector<rowType> x;
    std::sort(x.begin(),x.end(),comparator_generator.operator()<0>());
}

语法看起来有点滑稽。 comparator_generator.operator()&lt;0&gt;() 调用外部 lambda 来获取内部 lambda,它是实际的比较器。这再次很好地说明了模板是 operator()

【讨论】:

  • 这基本上是一个来自 js 的闭包。如果我选择使用 lambda 方式,似乎没有办法避免它。
  • @Devaniti js ?我不明白你想“避免”什么。您需要以某种方式修复索引,这不是您可以避免的
  • 将函数包装在另一个函数中以隐藏某种参数的概念很简单,JavaScript 是其中一种常见的语言。我不想避免它,似乎没有更简单的方法可以使用 lambdas 来做到这一点。
  • @Devaniti 答案的第二部分只是出于好奇。恕我直言,编写函子更具可读性。你从comparator_generator.operator()&lt;0&gt;()comparator&lt;0&gt;{reversed} 得到的基本上是相同的。第一个的唯一缺点是您不能在函数内定义模板。
【解决方案2】:

你误解了会发生什么。您使用模板化的 operator () 创建 lambda - 而不是 lambda 模板。没有专门的 lambda 并将其传递给其他地方。

您可以将 lambda 包装在其他 lambda 中,并根据列索引定制包装它。或者您可以只创建一个常规类模板 - 在您的情况下它足够简单明了。

【讨论】:

  • 现在我明白了。 sortLambda.operator()(rows[0], rows[1]);成功调用它。遗憾的是,它可能无法用作 lambda 进行排序。
【解决方案3】:

从这里开始:

template<auto x>
using constant_t=std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
constexpr constant_t<x> constant={};

现在很容易将编译时间常数传递给 lambda。

using rowType = std::tuple<size_t, const char*, unsigned int, unsigned int>;
bool reversed = false; // this is just an example of input I may have
auto sortLambda = [reversed](auto tupleIndex){
  return [reversed, tupleIndex](const rowType& a, const rowType& b) -> bool {
    if (reversed)
    {
      return std::get<tupleIndex>(a) < std::get<tupleIndex>(b);
    }
    return std::get<tupleIndex>(a) > std::get<tupleIndex>(b);
  };
};

那就做吧

std::vector<rowType> x;
std::sort(x.begin(),x.end(),sortLambda(constant<0>));

诀窍在于constant&lt;0&gt; 是一个整数常量,并且它有一个 constexpr 转换运算符。

Live example.

正如其他人所指出的,[] 之后的 lambda 模板参数位于 operator() 上。

这样的模板 lambda:

template<std::size_t n>
auto some_lambda = [capture](auto&&...args) {
};

在非全局范围内是不允许的。

我怀疑这是因为此类变量的生命周期有点古怪。如果任何代码分支在声明时可以实例化它,它是否会存在,或者它是否在第一次提到时就存在?并且他们会在通常的时间离开范围?

结果很奇怪。所以我明白他们为什么禁止它了。

【讨论】:

    猜你喜欢
    • 2013-02-13
    • 2021-02-07
    • 1970-01-01
    • 2020-01-23
    • 2016-04-27
    • 1970-01-01
    • 2013-06-15
    • 2021-05-16
    相关资源
    最近更新 更多