mdx*_*mdx 18 c++ templates translation std-function
我知道以下代码无法编译。
void baz(int i) { }
void baz() {  }
class Bar
{
    std::function<void()> bazFn;
public:
    Bar(std::function<void()> fun = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
    Bar b;
    return 0;
}
因为std::function据说不考虑重载决议,正如我在另一篇文章中读到的。
我不完全理解迫使这种解决方案的技术限制。
我在 cppreference 上阅读了翻译和模板的阶段,但我想不出任何我找不到反例的推理。向半个外行(C++ 的新手)解释一下,是什么以及在哪个翻译阶段导致上述无法编译?
Nic*_*las 14
这与“翻译阶段”没有任何关系。这纯粹是关于std::function.
看,std::function<R(Args)>不要求给定的函数完全是 type R(Args)。特别是,它不需要给它一个函数指针。它可以采用任何可调用类型(成员函数指针,某些具有 重载的对象operator()),只要它是可调用的,就好像它接受Args参数并返回可转换为的 某些内容R(或者如果R是void,则可以返回任何内容)。
为此, 的适当构造函数std::function必须是模板: template<typename F> function(F f);。也就是说,它可以采用任何函数类型(受上述限制)。
该表达式baz表示一个重载集。如果您使用该表达式来调用重载集,那很好。如果您使用该表达式作为采用特定函数指针的函数的参数,C++ 可以将重载集缩减为单个调用,从而使其很好。
但是,一旦函数是模板,并且您使用模板参数推导来确定该参数是什么,C++ 就不再能够确定重载集中的正确重载是什么。所以你必须直接指定它。
仅当 (a) 调用函数/运算符的名称,或 (b) 将其转换为具有显式签名的指针(指向函数或成员函数)时,才会发生重载解析。
两者都没有发生在这里。
std::function接受任何与其签名兼容的对象。它没有专门采用函数指针。(lambda 不是 std 函数,std 函数也不是 lambda)
现在在我的自制函数变体中,对于签名,R(Args...)我也R(*)(Args...)正是因为这个原因接受一个参数(完全匹配)。但这意味着它将“完全匹配”签名提升到“兼容”签名之上。
核心问题是重载集不是 C++ 对象。您可以命名重载集,但不能“本地”传递它。
现在,您可以创建一个函数的伪重载集,如下所示:
#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__(decltype(args)(args)...) )
这将创建一个可以对函数名称进行重载解析的单个 C++ 对象。
展开宏,我们得到:
[](auto&&...args)
noexcept(noexcept( baz(decltype(args)(args)...) ) )
-> decltype( baz(decltype(args)(args)...) )
{ return baz(decltype(args)(args)...); }
写起来很烦人。一个更简单,只是不太有用的版本在这里:
[](auto&&...args)->decltype(auto)
{ return baz(decltype(args)(args)...); }
我们有一个 lambda,它接受任意数量的参数,然后完美地将它们转发到baz.
然后:
class Bar {
  std::function<void()> bazFn;
public:
  Bar(std::function<void()> fun = OVERLOADS_OF(baz)) : bazFn(fun){}
};
作品。我们将重载解析推迟到我们存储的 lambda 中fun,而不是fun直接传递重载集(它无法解析)。
至少有一个提议在 C++ 语言中定义一种将函数名称转换为重载集对象的操作。在这样的标准提案出现在标准中之前,OVERLOADS_OF宏是有用的。
您可以更进一步,并支持强制转换为兼容函数指针。
struct baz_overloads {
  template<class...Ts>
  auto operator()(Ts&&...ts)const
  RETURNS( baz(std::forward<Ts>(ts)...) );
  template<class R, class...Args>
  using fptr = R(*)(Args...);
  //TODO: SFINAE-friendly support
  template<class R, class...Ts>
  operator fptr<R,Ts...>() const {
    return [](Ts...ts)->R { return baz(std::forward<Ts>(ts)...); };
  }
};
但这开始变得迟钝。
#define OVERLOADS_T(...) \
  struct { \
    template<class...Ts> \
    auto operator()(Ts&&...ts)const \
    RETURNS( __VA_ARGS__(std::forward<Ts>(ts)...) ); \
\
    template<class R, class...Args> \
    using fptr = R(*)(Args...); \
\
    template<class R, class...Ts> \
    operator fptr<R,Ts...>() const { \
      return [](Ts...ts)->R { return __VA_ARGS__(std::forward<Ts>(ts)...); }; \
    } \
  }
这里的问题是没有告诉编译器如何执行指针衰减函数。如果你有
void baz(int i) { }
void baz() {  }
class Bar
{
    void (*bazFn)();
public:
    Bar(void(*fun)() = baz) : bazFn(fun){}
};
int main(int argc, char **argv)
{
    Bar b;
    return 0;
}
然后代码就可以工作了,因为现在编译器知道你想要哪个函数,因为你分配了一个具体的类型。
当您使用时,std::function您调用它的函数对象构造函数,它具有以下形式
template< class F >
function( F f );
并且由于它是一个模板,它需要推断传递的对象的类型。由于baz是重载函数,因此无法推导出单一类型,因此模板推导失败并出现错误。你将不得不使用
Bar(std::function<void()> fun = (void(*)())baz) : bazFn(fun){}
获得强制单一类型并允许扣除。
| 归档时间: | 
 | 
| 查看次数: | 827 次 | 
| 最近记录: |