首页 > 解决方案 > 模板类型推导失败(std::empty 作为谓词)

问题描述

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

#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:注意:候选模板被忽略:无法推断模板参数'_Predicate'

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

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

好吧,走着瞧:

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

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

来自主干或 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)。

标签: c++c++17

解决方案


不幸的是,将重载的模板函数与模板函数区分开来存在问题std::all_of。更好的解释:std::function 无法区分重载函数

所以要做到这一点,static_cast 需要正确的函数类型bool ( * )( const std::vector< int >& )

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

推荐阅读