8

It compiles with /permissive but fails with /permissive-. What is not conforming and how to fix it?

Why it's fine in (2) but fails in (4)(3)? If I remove operator long it also fine.

How to fix it without changing call site (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 message

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)'
OwnageIsMagic
  • 680
  • 8
  • 23

3 Answers3

7

I suppose you meant it's fine in (2) but fails in (3)

Note that the #2 is initialization, which calls the constructor of std::string; the #3 is assignment, which calls the assignment operator of std::string. They're different things.

The invocation of assigment operator is ambiguous because the assignment operator of std::string has an overload taking char, which could be implicitly converted from long (which is a stardard conversion), then leads to ambiguity (with the assignment operators taking std::string, as the compiler complained). Both the implicit conversion sequence contain one user-defined conversion (from my to std::string or long), they have the same rank in onverload resolution.

The invocation of constructor is fine because it doesn't have such overload (taking char).

songyuanyao
  • 147,421
  • 15
  • 261
  • 354
3

The problem is that in the case #2 there is used a constructor while in the case #3 there is used an assignment operator.

The assignment operator is overloaded like

basic_string& operator=(charT c);

But there is no constructor that accepts only one argument of the type charT

So for the case #2 there is used the user-defined conversion operator

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

and then the constructor

basic_string(basic_string&& str) noexcept;

In the case #3 there are two posiibilities.

The first one is to call the conversion operator

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

and then the assignment operator

basic_string& operator=(basic_string&& str)

And the second one is to call the conversion operator

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

and then the assignment operator

basic_string& operator=(charT c);

It is interesting to note the following additional case.

If you will wrote

str.s = { my("ascas") };

then there will not be an ambiguity. The compiler will select the operator that accepts an std::initializer_list. That is it will select the assignment operator

basic_string& operator=(initializer_list<charT>);

In this case there will be used the conversion operator

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

but as the string "ascas" can not be converted to the type long a runtime error will occur

terminate called after throwing an instance of 'std::invalid_argument'
  what():  stol
Vlad from Moscow
  • 224,104
  • 15
  • 141
  • 268
0

Vlad from Moscow answer have a nice explanation. But there was no solution. Here it is using 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();
}
OwnageIsMagic
  • 680
  • 8
  • 23