C++花括号初始化作为参数调用了不同的构造函数。

4

一个最简单的示例是这个类:

class Chain {
    public:
        Chain(std::string name, std::vector<int> args)
        {
            assert(true);
        }
        Chain(std::string name, bool flag)
        {
            assert(false);
        }
};

使用方式如下:
int main() {
    auto ch = Chain("name", {});
    return 1;
}
Chain的第二个构造函数之前没有bool flag,我不得不添加它,现在代码Chain("name", {})调用第二个构造函数(之前调用的是第一个构造函数)。
我想改变第二个构造函数的参数顺序。
        Chain(bool flag, std::string name)
        {
            assert(false);
        }

即使这样做,它更倾向于将const char *转换为bool,并将{}转换为std::string,然后调用第一个构造函数。

有没有一种方法可以禁止它/强制使用调用第一个构造函数?


2
在我看来,这个例子应该能够让你相信这两个函数不应该互相重载。 - Brian Bi
@Brian 确实,我已经将第二个构造函数更改为静态方法 Chain parse_from_string(string, bool),这在整个程序上下文中甚至更有意义,但我还是很好奇是否还有其他方法。 - sejvlond
使用enum class代替bool怎么样?这甚至可以让它的含义更加明显(true是什么意思?false是什么意思?ChainFlag::DoThat更好)。 - firda
@firda 也正确,会进一步重构它 ;) - sejvlond
3个回答

3

使用显式类型。

auto ch1 = Chain("name", std::vector<int>{});
auto ch2 = Chain("name", true);

当然,这肯定能行,但请考虑我的情况:我向一个已经在项目中多处使用的类添加了新的构造函数,这些构造函数完全不同(考虑第二个构造函数甚至是 (bool, string)),但编译器选择的方式与我预期的完全不同,因此我正在寻找一种方法来禁止它(也许在将来)。 - sejvlond
@sejvlond,所以我正在寻找一种禁止它的方法 确定最可靠的方法是使用显式类型的参数。使用隐式转换的参数存在你现在遇到的陷阱。 - R Sahu

2

重载决议规则十分复杂:

以下任何一个重载都是可行的:

Chain(std::string name, std::vector<int> args); #1
Chain(std::string name, bool flag); #2a
Chain(bool flag, std::string name); #2b

for

Chain("name", {});

但是,#2a#2b都比#1更合适,特别是出于以下原因:

1)标准转换序列始终优于用户定义的转换序列或省略号转换序列。

因此,{} -> bool{} -> std::vector<int>更好,并且
const char* -> boolconst char* -> std::string更好
(而{} -> std::vector<int>等同于{} -> std::string)。
我建议在bool周围添加一个包装类,例如:
struct bool_wrapper
{
    // bool_wrapper() : b(false) {} // would make your call ambiguous.
    bool_wrapper(bool b) : b(b) {}

    operator bool() const {return b;}

    bool b = false;  
};

然后

Chain(std::string name, bool_wrapper flag); // To fix overload resolution

演示
带有歧义调用的演示


感谢提供的文档和更好的解释,甚至布尔包装器也比其他提议的解决方案更好。 - sejvlond

1
如果您无法修改对第一个构造函数的调用,例如 Chain("name", std::vector<int>{});,我可以提供两种方法。
如果对第一个构造函数的调用总是使用字符串字面量(而不是std :: string),则可以修改两个构造函数:
class Chain {
    public:
        Chain(const char* name, std::vector<int> args)
        {
            assert(true);
        }
        Chain(bool flag, const std::string& name)
        {
            assert(false);
        }
};

如果上述假设不成立,那么您可以向第二个构造函数添加虚拟参数:
class Chain {
    public:
        Chain(std::string name, std::vector<int> args)
        {
            assert(true);
        }
        Chain(std::string name, bool flag, bool dummy)
        {
            assert(false);
        }
};

还有一种无需修改构造函数的奖励解决方案:

class Chain {
    public:
        Chain(const std::string& name, std::vector<int> args)
        {
            assert(true);
        }
        template<typename T>
        Chain(const std::string& name, T flag);
};

template<>
Chain::Chain(const std::string& name, bool flag)
{
    assert(false);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接