首页 > 解决方案 > 避免在人为的模棱两可的重载函数调用中拼写出类型

问题描述

最小示例程序:

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

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

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

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

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

如果有助于排除某些替代方案,则实际代码确实涉及std::vector<int>并且std::vector<T>确实涉及使用包含单个整数表达式的花括号初始化列表的调用,但T它是用户定义的类型,而不是内置类型,并且表达式是不是一个常数值。

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

标签: c++overload-resolutionambiguous-callexplicit-constructor

解决方案


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

using I=std::vector<int>;

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

显然你不能对int自己做任何事情,即使是隐式转换int也可以跟随标准转换。但我们可以(当然)让 SFINAE 承担责任

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

然后你可以写

f({A{1}});

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

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


推荐阅读