使用lambda和全局函数调用range :: for_each

时间:2017-03-31 12:52:01

标签: c++ c++11 c++14 range-v3

以下代码按预期工作:

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,但老实说我无法理解这个问题。

2 个答案:

答案 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)...)(产生与宏相同的效果)。遗憾的是,decltypenoexcept功能被拒绝了。