Clan Uncertainty with custom conversion operator

I was developing a kind of adapter class when I came across a problem under clang. When both conversion operators for the lvalue-reference and rvalue links are defined, you get an ambiguity compilation error trying to jump from your class (when such code should be exact like

operator const T& () const& 

allowed only for lvalues ​​AFAIK). I reproduced the error with a simple example:

 #include <string> class StringDecorator { public: StringDecorator() : m_string( "String data here" ) {} operator const std::string& () const& // lvalue only { return m_string; } operator std::string&& () && // rvalue only { return std::move( m_string ); } private: std::string m_string; }; void func( const std::string& ) {} void func( std::string&& ) {} int main(int argc, char** argv) { StringDecorator my_string; func( my_string ); // fine, operator std::string&& not allowed func( std::move( my_string ) ); // error "ambiguous function call" } 

Compilation is excellent on gcc 4.9+, does not work in any version of clang. So the question is: is there any workaround? Is my understanding of const and rights functions?

PS: To clarify, we are talking about fixing the StringDecorator class itself (or finding a workaround for such a class as if it were library code). Please refrain from answers that invoke the T & & () operator directly or explicitly indicating the type of conversion.

+5
source share
3 answers

The problem is choosing the best viable function. In the case of the second call to func this means comparing 2 user-defined conversion sequences. Unfortunately, 2 user-defined transformation sequences do not differ from each other if they do not use the same user-defined conversion function or the constructor standard C ++ [over.ics.rank / 3]:

Two implicit conversion sequences of the same form are indistinguishable conversion sequences if only one of the following rules applies:

  • [...]

  • A user transformation sequence U1 is a better transformation sequence than another user transformation sequence U2 if they contain the same user-defined transformation function or constructor [...]

Since an rvalue can always be attached to a const lvalue reference, you will in any case get to this ambiguous call if the function is overloaded for const std::string& and std::string&& .

As you mentioned, my first answer, consisting in reusing all functions, is not a solution, since you are implementing a library. In fact, it is not possible to define proxy functions for all functions that take a string argument !!

Thus, you can make a deal between two imperfect decisions:

  • You delete operator std::string&&() && and you lose some optimization, or:

  • You publicly inherit from std :: string and remove 2 conversion functions, and in this case you expose your library to abuse:

     #include <string> class StringDecorator : public std::string { public: StringDecorator() : std::string("String data here" ) {} }; void func( const std::string& ) {} void func( std::string&& ) {} int main(int argc, char** argv) { StringDecorator my_string; func( my_string ); // fine, operator std::string&& not allowed func( std::move( my_string )); // No more bug: //ranking of standard conversion sequence is fine-grained. } 

Another solution is not to use Clang because it is a Clang error .

But if you need to use Clang, Tony Frolov's answer is a solution.

+3
source

Olive's answer is correct, because in this case the standard seems quite understandable. The solution I chose at the same time was to leave only one conversion statement:

 operator const std::string& () const& 

The problem exists because both conversion operators are considered viable. Therefore, this can be avoided by changing the type of the implicit argument of the lvalue conversion operator from const & to & :

 operator const std::string& () & // lvalue only (rvalue can't bind to non-const reference) { return m_string; } operator std::string&& () && // rvalue only { return std::move( m_string ); } 

But this interrupts the conversion from const StringDecorator , which makes its use inconvenient in typical cases.

This broken decision made me wonder if there was a way to specify a member function specifier that would make the conversion statement viable with a const lvalue object, but not with an rvalue. And I managed to achieve this by specifying an implicit argument for the const conversion operator as const volatile & :

 operator const std::string& () const volatile& // lvalue only (rvalue can't bind to volatile reference) { return const_cast< const StringDecorator* >( this )->m_string; } operator std::string&& () && // rvalue only { return std::move( m_string ); } 

Per [dcl.init.ref] / 5 , for a link to be initialized by linking to rvalue, the link must be a constant of a non-volatile lvalue link or a rvalue link:

While the lvalue reference and the const lvalue reference can communicate with the volatile reference constant. Obviously, the mutable modifier of a member function performs a completely different task. But hey, this works and is enough for my use. The only remaining problem is that the code becomes misleading and awesome.

+2
source

clang ++ is more accurate. Both func overloads are not exact matches for StringDecorator const& or StringDecorator&& . Therefore, my_string cannot be moved. The compiler cannot choose between possible conversions StringDecorator&& β†’ std::string&& β†’ func(std::string&&) and StringDecorator&& β†’ StringDecorator& β†’ std::string& β†’ func(const std::string&) . In other words, the compiler cannot determine at what step it should apply the cast operator.

I don't have g ++ to test my assumption. I suppose this goes the second way, since my_string cannot be moved, it applies the translation operator const& to StringDecorator& . You can check it if you add debug output to the bodies of lithium operators.

+1
source

Source: https://habr.com/ru/post/1273863/


All Articles