【问题标题】:Disambiguating an overloaded function with a given pack of parameters用给定的参数包消除重载函数的歧义
【发布时间】:2020-12-08 17:30:53
【问题描述】:

我正在尝试实现一个函数模板ovl,这样ovl<Foo, Bar>(f) 将返回f 的重载,采用(Foo, Bar),并且对我的幼稚解决方案所发生的事情感到非常惊讶:

template <class... Args, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Args>...)) { return f; }

void foo();
void foo(int);
void foo(int, int);

int main() {
    ovl<int>(foo)(0);
}
prog.cc:26:5: fatal error: no matching function for call to 'ovl'
    ovl<int>(foo)(0);
    ^~~~~~~~
prog.cc:6:16: note: candidate template ignored: couldn't infer template argument 'Ret'
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Args>...)) { return f; }
               ^

GCC 和 Clang 也会出现同样的错误。更重要的是,它在我自己枚举可能的arities时确实有效:


template <class Ret>
constexpr auto ovl(Ret (*const f)()) { return f; }

template <class Arg0, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Arg0>)) { return f; }

template <class Arg0, class Arg1, class Ret>
constexpr auto ovl(Ret (*const f)(std::type_identity_t<Arg0>, std::type_identity_t<Arg1>)) { return f; }

// ... ad nauseam.

Wandbox demo

有趣的是,保留 Args... 但硬编码返回类型也可以:

template <class... Args>
constexpr auto ovl(void (*const f)(std::type_identity_t<Args>...)) { return f; }

似乎在将部分显式参数提供给参数包时会忽略它们,但为什么呢?以及如何确保在尝试消除函数指针歧义时考虑它们?


注意:我发现了以下解决方法,它先烘焙 Args...,然后再推断 Ret,但我仍然对答案感兴趣,因为这很笨重。

template <class... Args>
struct ovl_t {
    template <class Ret>
    constexpr auto operator()(Ret (*const f)(Args...)) const { return f; }
};

template <class... Args>
constexpr ovl_t<Args...> ovl;

【问题讨论】:

  • 我怀疑这里的正确答案可能会进入语言律师领域。用language-lawyer 标记这个问题可能有助于注意
  • 你能详细说明一下笨重吗?也许还有其他一些解决方法。
  • @StoryTeller-UnslanderMonica 恐怕如果不是重复的,至少是相同的原因......该死。
  • @PasserBy my complete use cas 为成员函数(const,非const,合格)和可变参数提供了额外的重载和变体,并为每个函数生成了一个完整的类模板和变量模板案例感觉有点笨拙,如果有效 - 特别是因为我觉得我离一个基于功能的工作实现有点怪癖。

标签: c++ templates overloading variadic-templates template-argument-deduction


【解决方案1】:

我相信这是一个编译器错误。

正如@StoryTeller - Unslander Monica 提到的[temp.arg.explicit]

模板参数推导可以扩展与模板参数包对应的模板参数序列,即使该序列包含显式指定的模板参数。

这意味着即使我们提供了 int 参数,编译器也会尝试为 Args 推导出更多参数。

然而[temp.deduct.call]

如果参数是重载集(不包含函数模板),则尝试使用该集的每个成员进行试验参数推导。如果只有一个重载集成员的推导成功,则该成员将用作推导的参数值。如果对重载集的多个成员的推导成功,则将参数视为非推导上下文。

试用扣除是

template<typename... Args, typename Ret>
void trial(Ret(*)(std::type_identity_t<Args>...));

trial<int>((void(*)())foo);         // fails
trial<int>((void(*)(int))foo);      // succeeds
trial<int>((void(*)(int, int))foo); // fails, trailing Args is non-deduced context

暗示只有void(int)成员被用作参数值,这将成功推导出Ret = voidArgs = {int}

【讨论】:

  • 我不相信“成功”的说法。对于重载集中的每个参数,pack 参数都位于非推导上下文中。那么为什么空包是可推论的呢?
  • @StoryTeller-UnslanderMonica 我太不耐烦了,希望这次我不会搞砸。我相信是[temp.deduct.type]/10。否则,void f(int, std::vector&lt;Args&gt;...) 将无法被称为f(42),这……很奇怪。
猜你喜欢
  • 2022-07-26
  • 1970-01-01
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-19
相关资源
最近更新 更多