避免在人为的模棱两可的重载函数调用中拼写出类型

Posted

技术标签:

【中文标题】避免在人为的模棱两可的重载函数调用中拼写出类型【英文标题】:Avoid spelling out type in artificially amgibuous overloaded function call 【发布时间】:2019-02-27 09:33:15 【问题描述】:

最小示例程序:

#include <vector>
void f(std::vector<int>)     // #1
void f(std::vector<void *>)  // #2
int main()  f( 1 ); 

这是一个有效的程序直觉上是有意义的:使用重载 #1 的调用将是有效的,使用重载 #2 的调用将是格式错误的,因此应该选择重载 #1。这就是 clang 所做的。

不幸的是,按照标准,这似乎是模棱两可的,因为std::vector&lt;void *&gt; 的构造函数可以用int 调用,方法是将其隐式转换为size_t。在重载决议期间应该忽略构造函数explicit 的事实,如果选择该重载,程序将只是格式错误。 GCC 以模棱两可的方式拒绝该调用,并且这样做看起来是正确的。

我可以通过拼写类型名称来修改代码让 GCC 接受调用:f(std::vector&lt;int&gt; 1 );。我也可以使用带有默认参数的标签调度来明确指定要使用的重载,同时允许像以前一样接受现有调用。

这两个都是可以接受的,但是当回到真正的代码时会很快变得相当冗长。是否有另一个选项可以让我避免拼出完整的类型名称,但坚持使用当前的重载?我想了一会儿 1, 可能会起作用,但当然不会,int i = 1, ; 也完全有效,不能用来避免 #2。

如果有助于排除一些替代方案,实际代码确实涉及std::vector&lt;int&gt;std::vector&lt;T&gt;,并且确实涉及带有包含单个整数表达式的花括号初始化列表的调用,但T 是用户定义的类型, 不是内置类型,表达式也不是常量。

“否”是可以接受的答案,但在这种情况下,请详细说明,请说明没有这样的选项。

【问题讨论】:

也许在您的问题中将void f(std::vector&lt;void *&gt;) 更改为void f(std::vector&lt;char&gt;)?这与 void 指针无关。 @NeilButterworth 我选择了我能想到的最简单的类型,它不会接受来自int 的初始化。 char 确实接受来自 int 的初始化,因此不太能代表真实代码。 但正如你所说,void * 确实接受来自 int(或几乎任何东西)的初始化。 @NeilButterworth 嗯?不,void * 不接受来自int 的初始化。 void *p = 1; 显然无效。 @OZ17 我正在考虑如何在这里工作,但我没有看到它,抱歉。你能详细说明一下吗? 【参考方案1】:

总是很难证明是否定的,但考虑到引入的可能性

using I=std::vector<int>;

我认为您真的在问“有没有办法避免标准转换为 size_type 以消除争用中的其他重载”?

显然你不能对int 本身做任何事情,甚至隐式转换为int 之后也可以进行标准转换。但我们可以(当然)bring SFINAE to bear:

struct A 
  int i;
  template<class T,std::enable_if_t<std::is_same_v<T,int>>* =nullptr>
  operator T() const return i;
;

然后你就可以写了

f(A1);

(用户定义的文字会更简洁,但您说的是非常量表达式。)

这是否意味着是或否的答案取决于您,但我很确定没有其他方法可以在不允许标准转换的情况下使用大括号初始化器。

【讨论】:

以上是关于避免在人为的模棱两可的重载函数调用中拼写出类型的主要内容,如果未能解决你的问题,请参考以下文章

对重载函数错误的奇怪模棱两可的调用

使用 + (unary plus) 为 lambda 解决函数指针和 std::function 上的模棱两可的重载

为不明确的重载函数调用创建默认值

“运算符”的 C++ 模棱两可的重载

如何避免名称混淆?

重载运算与类型转换——函数调用运算符,重载类型转换与运算符