为什么我们不能在某些 STL 容器中不提及其类型而直接使用 lambda?
Why can't we directly use lambdas without mentioning its type in some STL containers?
我正在使用 STL 容器,试图实现自定义 comparators/predicates,我意识到当将谓词作为 Functor 提供时,所有容器的语法似乎都相同,即您只需提及仿函数作为模板参数中的相应参数,bam 它有效!
现在来到 Lambdas,它们在 std::vector
中的容器中工作得很好,但在 std::set
中却不行,语法真的很奇怪。这是一些与此相关的代码
对于 std::vector
这很好用
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a = { 3, 1, 0, 2 };
sort(a.begin(), a.end(), [](const int& num1, const int& num2) -> bool {
return num1 < num2;
});
for (const int& num : a)
{
cout << num << " ";
}
}
但是对于 std::set
这不起作用
auto PredicateLambda = [](const pair<int, string>& p1, const pair<int, string>& p2) -> bool {
if (p1.first != p2.first)
return p1.first < p2.first;
else
return p1.second < p2.second;
};
set<pair<int, string>, PredicateLambda> st;
相反,您将不得不做
set<pair<int, string>, decltype(PredicateLambda)> st(PredicateLambda);
我觉得这真的很奇怪,因为类型是在编译时确定的,手动指定似乎没有必要。我觉得我在这里遗漏了一些东西,如果有人可以填补为什么这样做的空白,我真的很感激?
P.S。我想到的一个解释是对谓词的类型进行了某种静态时间检查,但是,这让我想到了另一个问题,即在 Functors 的情况下如何进行类型检查,这似乎正常工作(即仅指定仿函数的名称)
编辑:问题的目的不是要了解语法,而是设计背后的推理。原因是 std::sort()
本质上只需要一个可调用对象,它可以比较两个值并使用它进行就地排序,而 std::set
class 必须在其 [=42] 中声明谓词=] 因此也需要对象的类型。当我们给出 Functor 的名称时,我们实际上是在给出 Class 名称(就像一个类型),但是 PredicateLambda
是一个对象,因此我们使用 decltype
来表示它的类型。
您混淆了函数模板和 class 模板。对于这两种情况,要出现任何代码,都必须对其进行实例化。让我们看看每个案例:
将std::sort
is a function template which accepts the binary-predicate (i.e. callable compare function/ function objects)作为其第三个参数。
template< class RandomIt, class Compare >
constexpr void sort( RandomIt first, RandomIt last, Compare comp ); (since C++20)
^^^^^^^^^^^^
这里编译器会尝试从传递的函数参数中确定模板参数类型(即RandomIt
和Compare
),即所谓的隐式实例化如果您没有明确提及它们,将自动发生。
这意味着,也可以使用模板参数调用 std::sort
(显式实例化):
const auto compare = [](const int& num1, const int& num2) { ...
std::sort<std::vector<int>::iterator, decltype(compare)>(a.begin(), a.end(), compare);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
而 std::set
is a class template。只要不在此处指定模板参数,就不会得到具体的类型。
A class template by itself is not a type, or an object, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).
template<
class Key,
class Compare = std::less<Key>, // optional
class Allocator = std::allocator<Key>
> class set;
此处,Compare
是可选的,但它是 std::set
的 class 模板类型的一部分,可以用用户定义的比较函数/可调用函数替换。如果不提供可调用的类型,编译器无法实例化您想要的 std::set
版本。
进一步阅读:
std::pair
has compare operations defined。因此,您的示例代码中不需要 PredicateLambda
。我希望它是为了演示目的。
因为c++17 we have deduction guide,对于模板class,可以简单地写成:
#include <set>
#include <tuple>
#include <string>
using namespace std::string_literals;
std::set st{ std::pair{1, "string"s} }; // st uses the
也可以自己提供deduction guides
因为 c++20 lambda 表达式是默认可构造的。因此,
在较新的版本中,您可能不需要将 PredicateLambda
作为参数传递给 st
编译器。
std::set<std::pair<int, std::string>, decltype(PredicateLambda)> st;
模板classes和函数的类型推导规则不同。
您正在看到 std::sort
推导其参数,包括谓词、类型。
与此同时,std::set
class 模板类型推导被阻止,因为您为其提供了模板参数。
在c++17中您可以使用演绎指南:
std::set s( {{1, "hello"s}}, PredicateLambda );
其中 PredicateLambda
的类型被移入集合。
回到c++11你可以使用函数指针
std::set<std::pair<int, std::string>, bool(*)(std::pair<int, std::string> const&)> s(
{{1, "hello"}}, PredicateLambda );
这依赖于无状态 lambda 可以隐式转换为具有匹配签名的函数指针这一事实。
如果你的 predicate 是 stateful(即捕获东西),你可以在 std::set
的类型中使用 std::function<bool(std::pair<int, std::string> const&)>
来存储 predicate 和不公开其类型。
现在,在 c++20 中,我们可以使您的语法更简单一些,如下所示:
#include <set>
#include <string>
auto PredicateLambda = [](auto& p1, auto& p2) -> bool {
if (p1.first != p2.first)
return p1.first < p2.first;
else
return p1.second < p2.second;
};
template<auto x>
struct call_t {
decltype(auto) operator()(auto&&...args)const {
return x(decltype(args)(args)...);
}
};
int main() {
std::set<std::pair<int, std::string>, call_t<PredicateLambda>> st;
st.insert( {0, "hello"} );
}
现在我们只提一次模板名称,不再使用decltype
。
我正在使用 STL 容器,试图实现自定义 comparators/predicates,我意识到当将谓词作为 Functor 提供时,所有容器的语法似乎都相同,即您只需提及仿函数作为模板参数中的相应参数,bam 它有效!
现在来到 Lambdas,它们在 std::vector
中的容器中工作得很好,但在 std::set
中却不行,语法真的很奇怪。这是一些与此相关的代码
对于 std::vector
这很好用
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a = { 3, 1, 0, 2 };
sort(a.begin(), a.end(), [](const int& num1, const int& num2) -> bool {
return num1 < num2;
});
for (const int& num : a)
{
cout << num << " ";
}
}
但是对于 std::set
这不起作用
auto PredicateLambda = [](const pair<int, string>& p1, const pair<int, string>& p2) -> bool {
if (p1.first != p2.first)
return p1.first < p2.first;
else
return p1.second < p2.second;
};
set<pair<int, string>, PredicateLambda> st;
相反,您将不得不做
set<pair<int, string>, decltype(PredicateLambda)> st(PredicateLambda);
我觉得这真的很奇怪,因为类型是在编译时确定的,手动指定似乎没有必要。我觉得我在这里遗漏了一些东西,如果有人可以填补为什么这样做的空白,我真的很感激?
P.S。我想到的一个解释是对谓词的类型进行了某种静态时间检查,但是,这让我想到了另一个问题,即在 Functors 的情况下如何进行类型检查,这似乎正常工作(即仅指定仿函数的名称)
编辑:问题的目的不是要了解语法,而是设计背后的推理。原因是 std::sort()
本质上只需要一个可调用对象,它可以比较两个值并使用它进行就地排序,而 std::set
class 必须在其 [=42] 中声明谓词=] 因此也需要对象的类型。当我们给出 Functor 的名称时,我们实际上是在给出 Class 名称(就像一个类型),但是 PredicateLambda
是一个对象,因此我们使用 decltype
来表示它的类型。
您混淆了函数模板和 class 模板。对于这两种情况,要出现任何代码,都必须对其进行实例化。让我们看看每个案例:
将std::sort
is a function template which accepts the binary-predicate (i.e. callable compare function/ function objects)作为其第三个参数。
template< class RandomIt, class Compare >
constexpr void sort( RandomIt first, RandomIt last, Compare comp ); (since C++20)
^^^^^^^^^^^^
这里编译器会尝试从传递的函数参数中确定模板参数类型(即RandomIt
和Compare
),即所谓的隐式实例化如果您没有明确提及它们,将自动发生。
这意味着,也可以使用模板参数调用 std::sort
(显式实例化):
const auto compare = [](const int& num1, const int& num2) { ...
std::sort<std::vector<int>::iterator, decltype(compare)>(a.begin(), a.end(), compare);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
而 std::set
is a class template。只要不在此处指定模板参数,就不会得到具体的类型。
A class template by itself is not a type, or an object, or any other entity. No code is generated from a source file that contains only template definitions. In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).
template<
class Key,
class Compare = std::less<Key>, // optional
class Allocator = std::allocator<Key>
> class set;
此处,Compare
是可选的,但它是 std::set
的 class 模板类型的一部分,可以用用户定义的比较函数/可调用函数替换。如果不提供可调用的类型,编译器无法实例化您想要的 std::set
版本。
进一步阅读:
std::pair
has compare operations defined。因此,您的示例代码中不需要PredicateLambda
。我希望它是为了演示目的。因为c++17 we have deduction guide,对于模板class,可以简单地写成:
#include <set> #include <tuple> #include <string> using namespace std::string_literals; std::set st{ std::pair{1, "string"s} }; // st uses the
也可以自己提供deduction guides
因为 c++20 lambda 表达式是默认可构造的。因此, 在较新的版本中,您可能不需要将
PredicateLambda
作为参数传递给st
编译器。std::set<std::pair<int, std::string>, decltype(PredicateLambda)> st;
模板classes和函数的类型推导规则不同。
您正在看到 std::sort
推导其参数,包括谓词、类型。
与此同时,std::set
class 模板类型推导被阻止,因为您为其提供了模板参数。
在c++17中您可以使用演绎指南:
std::set s( {{1, "hello"s}}, PredicateLambda );
其中 PredicateLambda
的类型被移入集合。
回到c++11你可以使用函数指针
std::set<std::pair<int, std::string>, bool(*)(std::pair<int, std::string> const&)> s(
{{1, "hello"}}, PredicateLambda );
这依赖于无状态 lambda 可以隐式转换为具有匹配签名的函数指针这一事实。
如果你的 predicate 是 stateful(即捕获东西),你可以在 std::set
的类型中使用 std::function<bool(std::pair<int, std::string> const&)>
来存储 predicate 和不公开其类型。
现在,在 c++20 中,我们可以使您的语法更简单一些,如下所示:
#include <set>
#include <string>
auto PredicateLambda = [](auto& p1, auto& p2) -> bool {
if (p1.first != p2.first)
return p1.first < p2.first;
else
return p1.second < p2.second;
};
template<auto x>
struct call_t {
decltype(auto) operator()(auto&&...args)const {
return x(decltype(args)(args)...);
}
};
int main() {
std::set<std::pair<int, std::string>, call_t<PredicateLambda>> st;
st.insert( {0, "hello"} );
}
现在我们只提一次模板名称,不再使用decltype
。