【问题标题】:Why does template argument deduction fail with overloaded function?为什么模板参数推导因函数重载而失败?
【发布时间】:2019-10-22 07:53:34
【问题描述】:

我有一个模板函数,它应该接受一个函数指针和参数,然后使用给定的参数调用函数指针(我们称之为Invoke)。但是,当我以重载函数作为参数调用模板函数时,模板推导失败。

我使用了 enable_if 以便只有一个重载有效,但这没有帮助。

#include <string>
#include <type_traits>

void foo(int, int){}
void foo(std::string, std::string) {}

template <bool Val1, bool Val2, bool ...Rest>
struct And
{
    enum {value = And<Val1 && Val2, Rest...>::value};
};
template <bool Val1, bool Val2>
struct And<Val1, Val2>
{
    enum {value = Val1 && Val2};
};

template <typename ...Params, typename ...Args, typename = typename std::enable_if<
    And<std::is_convertible<Args, Params>::value...>::value
>::type>
void Invoke(void (*fn)(Params...), Args ...args){}

int main() {
    Invoke(&foo, "a", "b");
    return 0;
}

试试ideone

当两个重载都存在时,编译器会抱怨mismatched argument pack lengths while expanding ‘std::is_convertible&lt;Args, Params&gt;::value’。 当我注释掉int 重载时,程序编译得很好,而当我注释掉std::string 重载时,推理失败,因为const char[] 不能隐式转换为int

标准(C++17 标准的第 17.8.2.1.6.2 节)说明如下:

如果参数是重载集(不包含函数模板),则试验参数推导是 尝试使用集合的每个成员。如果仅对其中一个重载集进行扣减成功 成员,该成员用作扣除的参数值。如果扣除成功更多 超过一个重载集的成员,参数被视为非推导上下文。

所以我希望编译器会尝试int 重载,其中推导会失败。当它尝试std::string重载时,推演成功。

由于只有一个重载集成员的推导会成功,我希望它会继续进行,就好像int 重载不存在一样,并且编译会像另一个重载时一样成功注释掉了,但它失败了。

我哪里错了?

参考标准将不胜感激。

【问题讨论】:

  • 我对 vardic 模板不是很熟悉,但 typename ...Params, typename ...Args, 似乎不合适。您希望编译器如何区分两者?但同样,我对 vardic 模板不太熟悉,所以也许我只是遗漏了一些东西。
  • 可以使用两个参数包,如果它们在推导的上下文中。参见例如stackoverflow.com/questions/9831501/…
  • 为什么不直接定义template &lt;bool... Args&gt; struct And :std::bool_constant&lt;(Args &amp;&amp; ...)&gt; {};
  • @L.F.因为我没有 C++17。另外,如果我这样做了,我可能根本不会定义struct And,而直接使用折叠表达式

标签: c++ language-lawyer template-argument-deduction


【解决方案1】:

这里的问题是有不止一个函数可以工作。 Params... 的扣除将在您到达模板的 SFINAE 部分之前发生。当它试图从void (*fn)(Params...) 中推断出Params.. 时,它匹配void foo(int, int)void foo(std::string, std::string)。由于它找到多个匹配项,因此 17.8.2.1.6.2 声明它被视为非推断上下文。

由于它无法推断类型,因此您会遇到硬停止错误。 SFINAE 只发生在模板参数推导步骤之后,在这种情况下它无法到达。

【讨论】:

  • 哦,现在我看到了,SFINAE 发生在重载解析期间,它发生在模板参数推导之后。这就是我一直在寻找的。谢谢
  • @Tomeamis 不完全是。编译器试图推断Params...,但它不能,所以它将它保留为未推断。然后它推导出Args...,然后移到最后一个参数。一旦它到达那里,您就会遇到一个严重错误,因为没有推断出 Params...,您需要它来尝试应用 SFINAE。
【解决方案2】:

&amp;foo 不是函数指针,而是重载集。你必须明确:

Invoke(static_cast<void(*)(std::string, std::string)>(&foo), "a", "b");

为了简化失败的enable_if,您可以采用未指定的函数指针类型,带有可变参数包,并检查is_invocablehttps://en.cppreference.com/w/cpp/types/is_invocable

【讨论】:

  • 这就是为什么我把关于重载集的标准引用放在问题中。你能详细说明为什么它不适用于这里吗?感谢 is_invocable,不过,我不知道。
猜你喜欢
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 2014-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-06
相关资源
最近更新 更多