繁体   English   中英

传递重载函数指针及其参数时的错误类型推导

[英]Bad type deduction when passing overloaded function pointer and its arguments

我正在尝试为std::invoke提供一个包装器,以完成推导函数类型的工作,即使函数已重载。
(我昨天为可变参数和方法指针版本问了一个相关问题)。

当函数有一个参数时,此代码 (C++17) 在正常重载条件下按预期工作:

#include <functional>

template <typename ReturnType, typename ... Args>
using FunctionType = ReturnType (*)(Args...);

template <typename S, typename T>
auto Invoke (FunctionType<S, T> func, T arg)
{   
    return std::invoke(func, arg);
}

template <typename S, typename T>
auto Invoke (FunctionType<S, T&> func, T & arg)
{   
    return std::invoke(func, arg);
}

template <typename S, typename T>
auto Invoke (FunctionType<S, const T&> func, const T & arg)
{
    return std::invoke(func, arg);
}

template <typename S, typename T>
auto Invoke (FunctionType<S, T&&> func, T && arg)
{   
    return std::invoke(func, std::move(arg));
}

更多的输入参数显然需要减少代码膨胀,但这是一个单独的问题。

如果用户的重载仅因常量/引用而不同,如下所示:

#include <iostream>

void Foo (int &)
{
    std::cout << "(int &)" << std::endl;
}

void Foo (const int &)
{
    std::cout << "(const int &)" << std::endl;
}

void Foo (int &&)
{
    std::cout << "(int &&)" << std::endl;
}

int main()
{
    int num;
    Foo(num);
    Invoke(&Foo, num);

    std::cout << std::endl;

    Foo(0);
    Invoke(&Foo, 0);
}

然后Invoke错误地推断出该函数,输出为 g++:

(int &)
(const int &)

(整数&&)
(const int &)

和叮当++:

(int &)
(const int &)

(整数&&)
(整数&&)

(感谢 geza 指出 clang 的输出不同)。

因此Invoke具有未定义的行为。

我怀疑元编程将是解决这个问题的方法。 无论如何,是否可以在Invoke站点正确处理类型推导?

理论

对于每个函数模板Invoke ,模板参数推导(必须成功才能考虑重载决议)考虑每个Foo以查看它是否可以为所涉及的一个函数参数( func )推导出多个模板参数(此处为两个)。 只有当恰好有一个Foo匹配时,整体推导才能成功(因为否则无法推导S )。 (这或多或少在评论中有所说明。)

第一个(“按值”) Invoke永远不会存活:它可以从任何Foo推导出来。 类似地,第二(“非const参考”)过载接受前两个Foo秒。 请注意,无论Invoke的其他参数如何(对于arg ),这些都适用!

第三个( const T& )重载选择相应的Foo重载并推导出T = int 最后一个与最后一个重载(其中T&&是正常的右值引用)做同样的事情,因此拒绝左值参数,尽管它的通用引用(在这种情况下将T推导出为int& (或const int& )并与func的推导冲突)。

编译器

如果arg是一个右值(并且像往常一样,不是 const),那么两个合理的Invoke重载在推导时都会成功,并且T&&重载应该获胜(因为它将右值引用绑定到一个rvalue )。

对于评论中的案例:

template <typename U>
void Bar (U &&);
int main() {
  int num;
  Invoke<void>(&Bar, num);
}

由于涉及函数模板,因此不会从&Bar推导,因此在每种情况下都成功推导了T (作为int )。 然后,对每种情况再次进行推导,以确定要使用的Bar (如果有),分别推导出Ufailint&const int&int& int&情况是相同的,而且明显更好,所以调用是模棱两可的。

所以Clang就在这里。 (但这里没有“未定义的行为”。)

解决方案

我没有给你一个通用的答案; 由于某些参数类型可以接受多个值类别/常量限定对,因此在所有这些情况下都不容易正确模拟重载解析。 已经有人提议以某种方式具体化重载集; 您可能会考虑这些方面的当前技术之一(例如每个目标函数名称的通用 lambda )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM