为什么 clang++ 不能推断出 lambda 映射的类型?

Why cannot clang++ deduce the type of a map of lambdas?

我有如下一段代码:

enum RelationalOperator { LT, LTE, EQ, GTE, GT };
std::map<RelationalOperator, bool (*)(const Point&, const Point&)> ops = {
    { GTE, [](const Point& a, const Point& b) { return a >= b; } },
    { LTE, [](const Point& a, const Point& b) { return a <= b; } },
    { EQ, [](const Point& a, const Point& b) { return a == b; } },
    { GT, [](const Point& a, const Point& b) { return a > b; } },
    { LT, [](const Point& a, const Point& b) { return a < b; } },
};

此代码在模板中,Point 是模板参数。

我试图用 auto 替换变量 ops 的类型,但 Clang++ 说:

src/utils.hpp:47:10: error: cannot deduce actual type for variable 'ops' with type 'auto' from initializer list

这是为什么?我认为关键字 auto 是针对这种类型很长且相当明显的情况。

auto 不适用于初始值设定项列表。相同的初始化列表可用于初始化一堆其他类型,例如:

std::map<int, bool (*)(const Point&, const Point&)>
std::multimap<RelationalOperator, bool (*)(const Point&, const Point&)>
std::vector<std::pair<int, bool (*)(const Point&, const Point&)>>

首先,每个 lambda 都有自己的类型,因此给定一组不同的 lambda,如果不进行一些手动转换(通常将它们嵌入 std::function<R(Args...)> 对象中),则不能将它们分解为单一类型。 =20=]

然后,当你写这样的初始化时:

enum RelationalOperator { LT, LTE, EQ, GTE, GT };
std::map<RelationalOperator, bool (*)(const Point&, const Point&)> ops = {
    { GTE, [](const Point& a, const Point& b) { return a >= b; } },
    { LTE, [](const Point& a, const Point& b) { return a <= b; } },
    { EQ, [](const Point& a, const Point& b) { return a == b; } },
    { GT, [](const Point& a, const Point& b) { return a > b; } },
    { LT, [](const Point& a, const Point& b) { return a < b; } },
};

到底发生了什么?它调用 std::map<RelationalOperator, bool (*)(const Point&, const Point&)>std::initializer_list 构造函数。它还能够推断出给定的大括号表达式是此类映射的初始化列表:

std::initializer_list<std::pair<RelationalOperator, bool (*)(Point const&, Point const&)>>

然后,对您的 lambda 进行隐式转换。

现在如果你改写:

auto ops = {
    { GTE, [](const Point& a, const Point& b) { return a >= b; } },
    { LTE, [](const Point& a, const Point& b) { return a <= b; } },
    { EQ, [](const Point& a, const Point& b) { return a == b; } },
    { GT, [](const Point& a, const Point& b) { return a > b; } },
    { LT, [](const Point& a, const Point& b) { return a < b; } },
};

无法弄清楚花括号表达式(std::initializer_list<T>中的T)代表的是什么对象。这在 gcc's error message:

中非常明确
main.cpp:8:29: error: unable to deduce 'std::initializer_list<auto>' from '{{1, 2}, {3, 4}}'

     auto x = {{1, 2}, {3, 4}};

                             ^

main.cpp:8:29: note:   couldn't deduce template parameter 'auto'

还可以考虑使用 STL 内置比较函子,即 std::equal_tostd::less_equalstd::greater_equalstd::lessstd::greater,它们都在 [=16 中可用=] 头文件.

例如:

#include <functional>
#include <map>

struct Point {
   bool operator <  (const Point &) const { /* actual implementation */ }
   bool operator <= (const Point &) const { /* actual implementation */ }
   bool operator >  (const Point &) const { /* actual implementation */ }
   bool operator >= (const Point &) const { /* actual implementation */ }
   bool operator == (const Point &) const { /* actual implementation */ }
   /* other stuff */
};

int main() {
   enum RelationalOperator { LT, LTE, EQ, GTE, GT };
    std::map<RelationalOperator, std::function<bool(const Point&, const Point&)>> ops = {
    {GTE, std::greater_equal<Point>()},
    {LTE, std::less_equal<Point>()},
    {EQ, std::equal_to<Point>()},
    {GT, std::greater<Point>()},
    {LT, std::less<Point>()},
   };
}

I thought that the keyword auto was for these kinds of situations, where the type is long and fairly obvious.

类型一点都不明显。每个 lambda 表达式都会产生一个唯一的匿名闭包类型,因此初始化列表的每个元素都有不同的类型:

auto ops = {
    { GTE, lambda_type_1 },
    { LTE, lambda_type_2 },
    { EQ, lambda_type_3 },
    { GT, lambda_type_4 },
    { LT, lambda_type_5 },
};

每个花括号初始化器都没有共同点。没有什么明显的。

当您初始化 std::map 时,有一个采用 std::initializer_list<value_type> 的构造函数,编译器可以将每个初始化程序转换为该类型。当您将映射替换为 auto 时,编译器无法使用任何线索来确定您希望它从不相关类型列表中推断出什么类型。