为何此模板类可正常运行?解析std::less的特殊用法
关于
std::less的几个疑问与原理解析 问题背景
可正常运行的代码示例1
#include <iostream> constexpr static auto less = std::less{}; #include <string> std::string a = "1"; std::string b = "1"; int main(){ std::cout << ( less( 6, 3 ) ? "Y" : "N" ) << '\n'; std::cout << ( less( a, b ) ? "Y" : "N" ) << '\n'; }
cppreference上的简化实现
cppreference给出的std::less简化实现如下:
template<class T> struct Less{ constexpr bool operator()(const T &lhs, const T &rhs) const{ return lhs < rhs; } };
按这个实现,std::less{};似乎必须指定具体类型(比如std::less<int>{})才能生效,但上面的代码却能正常运行。如果换成下面这种无类模板参数、仅operator()模板化的实现,逻辑就通顺了:
struct Less{ template<class T> constexpr bool operator()(const T &lhs, const T &rhs) const{ return lhs < rhs; } };
但如果实际是这种实现,为什么标准库不用lambda而非要定义类?
看似矛盾的代码示例2
下面这段代码被描述为可运行:
#include <iostream> constexpr static auto less = std::less<int>{}; #include <string> std::string a = "1"; std::string b = "1"; int main(){ // obviously for strings it no longer works. // std::cout << ( less( 6, 3 ) ? "Y" : "N" ) << '\n'; std::cout << ( less( a, b ) ? "Y" : "N" ) << '\n'; }
这背后的原理到底是什么?
原理解析
1. std::less{}能生效的核心:C++17的类模板实参推导+void特化
cppreference的简化实现没有展示标准库的完整细节。真实的std::less包含一个针对void的特化版本,结合C++17引入的类模板实参推导(CTAD),让std::less{}可以正常工作:
标准库的实际实现大概是这样的:
// 主模板 template <class T = void> struct less { constexpr bool operator()(const T& lhs, const T& rhs) const { return lhs < rhs; } }; // void特化版本 template <> struct less<void> { template <class T, class U> constexpr auto operator()(T&& lhs, U&& rhs) const noexcept(noexcept(std::forward<T>(lhs) < std::forward<U>(rhs))) -> decltype(std::forward<T>(lhs) < std::forward<U>(rhs)) { return std::forward<T>(lhs) < std::forward<U>(rhs); } };
当你写std::less{}时,CTAD会自动推导使用less<void>特化版本。这个版本的operator()是模板化的,能接受任意类型的参数(只要lhs < rhs合法),所以可以同时处理int和std::string的比较。
2. 为什么不用lambda替代std::less类?
- 历史原因:
std::less在C98就已经存在,而lambda是C11才引入的特性,标准库不可能用后出的特性替换早已存在的组件。 - 类型明确性:
std::less是命名的、明确的类型,能直接作为容器的模板参数(比如std::set<int, std::less<int>>);而lambda的类型是匿名的,必须用decltype才能作为这类场景的参数,使用起来不够直观。 - 接口一致性:标准库的比较器(
std::less、std::greater等)遵循统一的设计模式,保持了接口的一致性,降低了开发者的学习成本。
3. 关于std::less<int>{}调用less(a,b)的说明
你提供的这段代码正常情况下应该编译失败:std::less<int>的operator()仅接受const int&类型的参数,而std::string没有隐式转换为int的逻辑。如果你的编译器能通过并运行,大概率是编译器的非标准扩展,或者代码存在笔误(比如误写为std::less{}而非std::less<int>{})。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

