MSVC /permissive- 下 std::string 的重载运算符 '=' 有歧义

8

使用 /permissive 编译成功,但使用 /permissive- 失败。哪些地方不符合规范,如何解决?

为什么在 (2) 中正常,而在 (4)(3) 中失败? 如果删除 operator long 也会正常。

如何在不更改调用站点 (3,4) 的情况下解决这个问题?

#include <string>
struct my
{
    std::string myVal;
    my(std::string val): myVal(val) {}

    operator std::string() { return myVal; };
    operator long() { return std::stol(myVal); };
};
int main()
{
    struct MyStruct
    {
        long n = my("1223"); // (1)
        std::string s = my("ascas"); // (2)
    } str;
    str.s = my("ascas"); // (3)
    str.n = my("1223"); // (4)
}

错误信息
error C2593: 'operator =' is ambiguous
xstring(2667): note: could be 'std::basic_string<...> &std::basic_string<...>::operator =(const _Elem)'
        with
        [
            _Elem=char
        ]
xstring(2648): note: or 'std::basic_string<...> &std::basic_string<...>::operator =(const std::basic_string<...> &)'
xstring(2453): note: or 'std::basic_string<...> &std::basic_string<...>::operator =(std::basic_string<...> &&) noexcept(<expr>)'
Source1.cpp(17): note: while trying to match the argument list '(std::string, my)'

可能修复的一种方法是:str.s = my("ascas").operator std::string();(虽然我很少在实际应用中看到这种方法)。 - Eljay
现场示例。MSVC编译成功结束。 - Marek R
@cpplearner fixed - Marek R
这就是为什么显式地定义转换运算符通常是一件好事的原因。 - rubenvb
@rubenvb 但我希望它是隐式的。 - OwnageIsMagic
显示剩余4条评论
3个回答

7

我想你的意思是它在(2)中没问题,但在(3)中失败了

请注意,#2是初始化,调用了std::string的构造函数;#3是赋值,调用了std::string的赋值运算符。它们是不同的东西。

调用赋值运算符是有歧义的,因为std::string的赋值运算符有一个重载,接受char类型的参数,而long可以隐式转换为char(这是一种标准的转换),因此导致了歧义(与接受std::string类型参数的赋值运算符发生冲突,正如编译器所抱怨的那样)。两个隐式转换序列都包含一次用户定义的转换(从mystd::stringlong),它们在重载决议中具有相同的排名。

调用构造函数是没有问题的,因为它没有这样的重载(接受char类型参数)。


3
问题在于,在情况#2中使用了构造函数,而在情况#3中使用了赋值运算符。
赋值运算符被重载为如下:
basic_string& operator=(charT c);

但是没有接受只有一个charT类型参数的构造函数

因此,对于情况#2,使用了用户定义的转换运算符

operator std::string() { return myVal; };

接着是构造函数

basic_string(basic_string&& str) noexcept;

在第三种情况下有两种可能。
第一种是调用转换运算符。
operator std::string() { return myVal; };

然后是赋值运算符

basic_string& operator=(basic_string&& str)

第二种方法是调用转换操作符。

operator long() { return std::stol(myVal); };

然后是赋值运算符

basic_string& operator=(charT c);

有趣的是还有一个相关案例。如果您写下以下代码:
str.s = { my("ascas") };

那么就不会存在歧义。编译器将选择接受 std::initializer_list 的运算符,也就是它将选择赋值运算符。

basic_string& operator=(initializer_list<charT>);

在这种情况下,将使用转换操作符。
operator long() { return std::stol(myVal); };

但是当字符串"ascas"无法转换为长整型时,会产生运行时错误。

terminate called after throwing an instance of 'std::invalid_argument'
  what():  stol

permissive 是如何解决的?如何在不更改调用站点(3,4)的情况下修复它? - OwnageIsMagic
@OwnageIsMagic 很抱歉,我不处理 MS VS 相关的问题。 - Vlad from Moscow

0

莫斯科的弗拉德给出了一个很好的解释。但是没有解决方案。 这里使用SFINAE

template<typename T = long, typename = std::enable_if_t<
    std::is_same_v<T, long> || std::is_same_v<T, int>>>
operator T() const
{
    return l();
}

operator std::string() const
{
    return s();
}

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