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

Invoking ranges::for_each with lambda and global function

以下代码按预期工作:

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

如果不明确指定您想要的 overload/instantiation,您不能获取 overloadedtemplate 函数的地址。

在您的第一个代码段中传递 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);

函数名不代表函数;是函数名的重载集

你的函数

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 功能被否决了。