以下代码按预期工作:
void foo(auto const &){}
auto const rng{ranges::view::all(v)};
ranges::for_each(rng, [](auto const & r){
foo(r);
});
但是以下内容:
void foo(auto const &){}
auto const rng{ranges::view::all(v)};
ranges::for_each(rng, &foo);
给出编译错误:
template argument deduction/substitution failed:
couldn't deduce template parameter 'F'
我看了source,但老实说我无法理解这个问题。
答案 0 :(得分:3)
如果没有明确指定所需的重载/实例化,则无法获取重载或模板函数的地址。
在第一个代码段中传递 lambda表达式是解决此问题的好方法。
或者,如果您有权访问foo
,则可以将其转换为功能对象:
namespace detail
{
struct foo_t
{
template <typename T>
void operator()(const T&) const { /* ... */ }
};
}
constexpr detail::foo_t foo{};
这允许你写:
ranges::for_each(rng, foo);
答案 1 :(得分:3)
函数名称不代表函数;它是函数名的重载集。
你的功能
void foo(auto const &){}
似乎正在使用简洁的语言扩展来简化模板功能。在标准C ++中,它将显示为:
template<class T>
void foo(T const &){}
模板功能不是一个功能。它是由该模板生成的一组重载函数。
函数名称的重载集不是C ++对象。你可以传递给函数的东西是C ++对象。
现在,当函数名的重载集只命名一个函数时,编译器会自动解析重载并为您提供该函数对象。
当函数名的重载集转换为具有固定签名的函数指针时,重载决策启动并且(希望)选择了一个。
但是,在调用for_each
时,参数不是特定的固定签名函数指针。相反,它是一个泛型类型参数。此时,编译器无法解析函数名的重载集。你想要哪一个是模棱两可的。
有两种解决方案。其中之一是将函数重载集转换为特定的函数指针。这要求你明确。
第二个是将重载集顶部包装到单个对象中。您可以使用带有模板operator()
的手动功能对象执行此操作,或者在C ++ 14中执行此操作:
#define OVERLOADS_OF(...) \
[](auto&&...args) \
noexcept(noexcept(__VA_ARGS__(decltype(args)(args)...))) \
->decltype( __VA_ARGS__(decltype(args)(args)...) ) \
{ return __VA_ARGS__(decltype(args)(args)...); }
构建一个无状态lambda,表示全局函数名的重载。
所以:
void foo(auto const &){}
auto const rng{ranges::view::all(v)};
ranges::for_each(rng, OVERLOADS_OF(foo));
如果你想抓住更少的角落案例,那就简单了:
void foo(auto const &){}
auto const rng{ranges::view::all(v)};
ranges::for_each(rng, [](auto&x){foo(x);});
也适用于这种特殊情况。
顺便说一下,有一个C ++ 20提议用OVERLOADS_OF(foo)
替换[](auto&&...args)=>foo(decltype(args)(args)...)
(产生与宏相同的效果)。遗憾的是,decltype
和noexcept
功能被拒绝了。