【问题标题】:template type deduction failing (std::empty as a predicate)模板类型推导失败(std::empty 作为谓词)
【发布时间】:2018-05-28 12:50:55
【问题描述】:

我有一个向量向量,我想检查它们是否都是空的。使用标准库,我尝试了:

#include <algorithm>
#include <vector>

int main()
{
   std::vector<std::vector<int>> vv;

   std::all_of(std::begin(vv), std::end(vv), std::empty);
}

这会导致clang 7.0:中出现以下错误

/usr/bin/../lib/gcc/x86_64-linux-gnu/6.3.0/../../../../include/c++/6.3.0/bits/stl_algo.h :508:5: 注意:候选模板被忽略:无法推断模板参数 '_谓词'

我想这是一种标准行为,由于类型推导的规则。但无论如何,解决这个问题的最简单方法是什么?

编辑:我接受了 rubenvb 的回答,因为他给出了简单合理的解释,以及自然的解决方法。 all_of 接受一个谓词,它是一个函数、一个函数对象或一个 lambda 表达式。 std::empty 不是那些,而是一个函数模板。当显式实例化它时,我们得到一个应该可以工作的普通函数。令人惊讶的是,它仍然无法在我尝试过的大多数编译器上编译。

好吧,让我们看看:

在 GCC 6.3 上,它编译得很好 - https://godbolt.org/g/Pxta7C

但在来自主干的 GCC 上,它会导致内部编译器错误 - https://godbolt.org/g/H6DHt5

trunk 或 MSVC 2017 中的 Clang 均无法成功编译:

https://godbolt.org/g/819pbQ (Clang)

https://godbolt.org/g/ua5E8e (MSVC)

EDIT2:显然,Robert Andrzejuk 也是对的:编译器无法处理它的原因是一个模棱两可的重载决议。 std::empty 有 3 种不同的重载。其中两个同样是很好的候选者:一般的一个和 std::initializer 列表一个。我使用以下最小版本获得了类似的结果:

#include <vector>

template<class T>
void foo(const T& t);

template<class T>
void foo(const std::initializer_list<T>& il);

template<class F>
void bar(F f);


int main()
{
   bar(foo<std::vector<int>>);
}

不过,有一个区别。这个例子根本没有在 GCC 中从主干编译(而不是导致 ICE)。

【问题讨论】:

  • 但是错误是什么?你发布的是笔记
  • std::empty 是一个模板。 all_of 的第三个参数不是模板,而是闭包或可调用对象。您必须实例化模板:std::empty&lt;std::vector&lt;int&gt;&gt;{}。有可能只是 std::empty{} 也可以工作,没有费心去检查。
  • 谢谢。但是,它仍然是一个函数模板,而不是类模板。所以这两个都不起作用。
  • @Shmuel 你是对的,看我的回答。只需删除大括号初始化{}

标签: c++ c++17


【解决方案1】:

不幸的是,因为std::all_of 也是一个模板函数,所以在区分重载的模板函数时存在问题。更好的解释:std::function fails to distinguish overloaded functions

为此,需要 static_cast 到正确的函数类型:bool ( * )( const std::vector&lt; int &gt;&amp; )

std::all_of( vv.begin(), vv.end(),
             static_cast< bool ( * )( const std::vector< int >& ) >( std::empty ) );

使用有关所需static_cast 的知识,我们可以创建一个帮助模板函数来从容器类型中推断出正确的定义:

辅助函数:

template< typename C >
inline auto overloaded_pred_for( const C&, bool ( *f )( const C& ) ) -> decltype( f )
{
    return f;
}

使用示例:

std::all_of( vv.begin(), vv.end(), 
             overloaded_pred_for( std::vector< int >(), std::empty ) );

【讨论】:

    【解决方案2】:

    快速解决方法

    #include <algorithm>
    #include <vector>
    
    int main() {
       std::vector<std::vector<int>> vv;
    
       std::all_of(std::begin(vv), std::end(vv), 
        [](const auto &v) {return std::empty(v);});
    }
    

    【讨论】:

      【解决方案3】:

      解决这个问题的最简单方法是什么?

      也许

      std::all_of(std::begin(vv), std::end(vv),
                  [](auto const & v){ return v.empty(); });
      

      ?

      【讨论】:

      • @sop - 你的意思是std::empty(v)?我想它会起作用,但std::empy(v) 调用v.empty() 所以我认为最好直接调用v.empty()(或者避免在不使用lambda 的情况下调用std::empy()
      【解决方案4】:

      std::empty is a function template,因此本身不是可调用对象。通过显式提供模板参数,您可以将函数模板名称转换为可调用的具体实例:

      #include <algorithm>
      #include <iterator>
      #include <vector>
      
      int main()
      {
         std::vector<std::vector<int>> vv;
      
         std::all_of(std::begin(vv), std::end(vv), std::empty<std::vector<int>>);
      }
      

      Live demo (which incidentally crashes the compiler)。另请注意,此 GCC 版本显然需要 #include &lt;iterator&gt;,即使它明确提到 std::empty 应该与例如#include &lt;vector&gt;...

      【讨论】:

      • 听起来很合理,但它仍然会导致 gcc(主干)上的 ICE,并且根本无法在 clang 7 中编译。这是两个编译器的错误吗?
      • @ShmuelHanoch 嗯,这很奇怪。使用另一个函数模板(在非常有限的情况下模拟std::empty)确实有效,所以至少我确信语法是正确的,但显然不是你想要的:coliru.stacked-crooked.com/a/a5f1734b3175aeaf。奇怪的是,三巨头似乎都被打破了。
      • 这不适用于三大编译器(gcc、clang、msvc - godbolt.org/g/AAKdYh)。请参阅我的答案以了解原因。
      • @ShmuelHanoch 请不要接受此答案,以便我将其删除。我弄错了,没有意识到 std::empty 是一个重载函数(这将是有问题的)。
      【解决方案5】:

      问题是没有 std::empty 这样的东西,据我所知,它是一个成员函数。尝试使用[](const auto&amp; i){ return i.empty(); } 而是。

      编辑: 好吧,我之前没见过 std::empty 但正如下面的评论者指出的那样,它存在,但你仍然可以使用 lambda

      【讨论】:

      • std::empty 可从 C++17 获得。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多