【问题标题】:Why cannot clang++ deduce the type of a map of lambdas?为什么 clang++ 不能推断出 lambda 映射的类型?
【发布时间】:2016-07-28 09:47:11
【问题描述】:

我有以下代码:

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 是针对这类情况,类型较长且相当明显。

【问题讨论】:

  • 您想要map 而不是multimap 或结构数组,这并不明显。
  • 请提供mcve 以获取导致问题的实际代码。
  • @BoPersson 糟糕,我没有考虑到其他对象可能具有相同的初始化列表!

标签: c++ lambda c++14 auto


【解决方案1】:

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&)>>

【讨论】:

    【解决方案2】:

    首先,每个 lambda 都有自己的类型,因此给定一组不同的 lambda,如果不进行一些手动转换(通常将它们嵌入到 std::function&lt;R(Args...)&gt; 对象中),就无法将它们分解为单一类型。

    那么,当你写这样一个初始化:

    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&lt;RelationalOperator, bool (*)(const Point&amp;, const Point&amp;)&gt;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&lt;T&gt; 中的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'
    

    【讨论】:

      【解决方案3】:

      我认为关键字auto 是针对这类情况,类型较长且相当明显。

      类型根本不明显。每个 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&lt;value_type&gt; 并且编译器可以将每个初始化程序转换为该类型。当您将映射替换为 auto 时,编译器无法确定您希望它从不相关类型列表中推断出的类型。

      【讨论】:

        【解决方案4】:

        还可以考虑使用 STL 内置的比较函子,即 std::equal_tostd::less_equalstd::greater_equalstd::lessstd::greater,它们都可以在 &lt;functional&gt; 头文件中找到。

        例如:

        #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>()},
           };
        }
        

        【讨论】:

          猜你喜欢
          • 2012-01-15
          • 1970-01-01
          • 1970-01-01
          • 2021-03-11
          • 1970-01-01
          • 2015-10-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多