使用给定的参数包消除重载函数的歧义

Que*_*tin 6 c++ templates overloading variadic-templates template-argument-deduction

我正在尝试实现一个函数模板ovl,它ovl<Foo, Bar>(f)会返回ftake的重载(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);
}
Run Code Online (Sandbox Code Playgroud)
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; }
               ^
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 出现相同的错误。更重要的是,它实际上在自己枚举可能的参数时起作用:


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.
Run Code Online (Sandbox Code Playgroud)

魔杖盒演示

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

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

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


注意:我发现了以下解决方法,它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;
Run Code Online (Sandbox Code Playgroud)

Pas*_* By 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
Run Code Online (Sandbox Code Playgroud)

意味着仅void(int)使用成员作为参数值,这将成功推导Ret = voidArgs = {int}