Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass temporary object with standard constructor

I'd like to pass a temporary object(std::string for example) to the constructor of my object:

class MyClass{
  public:
    MyClass(string a):
      a(a)
    {

    }
    string a;
};

int main(int argc, char *argv[]){
  MyClass a(string());
  cout<<a.a<<endl;
  return 0;
}

But I receive this error:

main.cpp: In function ‘int main(int, char**)’:
main.cpp:28:11: error: request for member ‘a’ in ‘a’, which is of non-class type ‘MyClass(std::string (*)()) {aka MyClass(std::basic_string<char> (*)())}’

Everything works ok if I pass anything to the constructor of temporary object(for example string("")). Why?

like image 645
Dejwi Avatar asked Feb 29 '12 02:02

Dejwi


2 Answers

This is an instance of what has been dubbed C++'s most vexing parse. The compiler interprets

MyClass a(string());

as the prototype of a function named a returning a MyClass and taking an unnamed string as a parameter.

You can disambiguate it by putting parenthesis around the call to strings constructor:

MyClass a((string()));

Or, as long as MyClass has an accessible copy constructor:

MyClass a = string();

Update for C++11

You can also disambiguate with the universal initialisation syntax, as long as your class doesn't have a constructor that takes an initializer_list:

MyClass a { string() };
like image 153
Seth Carnegie Avatar answered Sep 23 '22 06:09

Seth Carnegie


As it has already been pointed out by Seth, that is a problem with the language and how the expression is parsed. Basically when the compiler finds the expression MyClass a(string()) it interprets it as the declaration of a function a that has the signature MyClass (std::string (*)()) (The extra (*) comes from an implicit conversion from function to pointer to function in arguments).

There are different approaches to overcome that syntax in your particular case:

MyClass a("");                // 1
MyClass b = std::string();    // 2
MyClass c(( std::string() )); // 3

The first approach is not to use the T() expression to create the rvalue, but rather a constant literal that will produce the same output. The shortcoming of this approach is that in this particular case you can use a literal, but the same solution cannot be applied to other types (i.e. if you had your own type that only had a default constructor)

The second approach is avoiding direct initialization, as the alternative syntax cannot be parsed as a function declaration. The problem with this approach is that while the result in your particular case is the same, it requires the constructor not to be explicit. (I.e. it would fail if your constructor was declared explicit MyClass( std::string const & )), but it is none

The third approach is adding an extra set of parenthesis around the first argument to the constructor (note that the same problem in parsing would happen with MyClass a(std::string(), std::string())). The problem with this approach (opinion) is that it is ugly, but it is otherwise the most flexible of the three.

like image 25
David Rodríguez - dribeas Avatar answered Sep 22 '22 06:09

David Rodríguez - dribeas