Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User-defined conversion function and casting to reference

I am encountering a compilation error for the following code:

class SymbolGroup
{
  std::string d_;

public:
  SymbolGroup(std::string a):d_(a){}

  // explicit operator const std::string&() const { return d_;} // compiles
  explicit operator std::string() const { return d_;} // Does not compile
};

inline
bool operator==(const SymbolGroup& lhs, const SymbolGroup& rhs)
{
  return static_cast<const std::string&>(lhs) ==
    static_cast<const std::string&>(rhs);
}

int main(){

  SymbolGroup a("hello");
  SymbolGroup b("hello");

  if (a==b)
    std::cout << "they are the same\n";

  return 0;
}

Without the 'const' and '&' in the user-defined type conversion line, it does not compile in g++ (4.8) with --std=c++11 flag:

error: invalid initialization of reference of type ‘std::string& {aka std::basic_string&}’ from expression of type ‘const string {aka const std::basic_string}’ explicit operator std::string&() const { return d_;}

The code compiles on Clang both ways. Which compiler is correct? Should this code compile with operator std::string()?

like image 583
ENIGMA Avatar asked Jul 15 '15 11:07

ENIGMA


Video Answer


1 Answers

UPDATE My previous answer was exactly wrong. Apologies! tldr; clang is correct to accept the code, gcc is incorrect to reject it.


First, from [expr.static.cast]:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5).

So effectively we're trying to direct-initialize an object of type std::string const& explicitly from an object of type SymbolGroup const&. There is a section specifically on initializing references by a conversion function: "Initialization by conversion function for direct reference binding" [over.match.ref]:

Under the conditions specified in 8.5.3, a reference can be bound directly to a glvalue or class prvalue that is the result of applying a conversion function to an initializer expression. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the underlying type of the reference being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

— The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that [...] are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type “lvalue reference to cv2 T2” or “cv2 T2” or “rvalue reference to cv2 T2”, respectively, where T2 is the same type as T or can be converted to type T with a qualification conversion (4.4), are also candidate functions.

The first part doesn't apply since our conversion function is explicit, so I omitted it. The second part does. We have cv1 T is const std::string, so our conversion function to std::string is a candidate function because std::string can be converted to const std::string with a qualification conversion.


gcc is wrong here, and I filed bug 66893, confirmed by our very own C++ expert and all around good guy Jonathan Wakely as well as the head Clang developer and C++ standard editor Richard Smith (after I thoroughly embarrassed myself filing a Clang bug).

like image 174
Barry Avatar answered Sep 30 '22 07:09

Barry