Absence of compilation error when using parametrized constructor
Asked Answered
D

2

18

Today at work I came across a behavior in C++ which I don't understand. I have produced the following example code to illustrate my problem:

#include <string>
#include <iostream>

class MyException
{
    public:
        MyException(std::string s1) {std::cout << "MyException constructor, s1: " << s1 << std::endl;}
};

int main(){
    const char * text = "exception text";
    std::cout << "Creating MyException object using std::string(const char *)." << std::endl;
    MyException my_ex(std::string(text));
    std::cout << "MyException object created." << std::endl;
    //throw my_ex;

    std::string string_text("exception text");
    std::cout << "Creating MyException object using std::string." << std::endl;
    MyException my_ex2(string_text);
    std::cout << "MyException object created." << std::endl;
    // throw my_ex2;

    return 0;
}

This code snippet compiles without any errors and produces the following output:

 $ g++ main.cpp
 $ ./a.out
Creating MyException object using std::string(const char *).
MyException object created.
Creating MyException object using std::string.
MyException constructor, s1: exception text
MyException object created.

Note that for my_ex the constructor I have defined was not called. Next, if I want to actually throw this variable:

throw my_ex;

I get a compilation error:

 $ g++ main.cpp
/tmp/ccpWitl8.o: In function `main':
main.cpp:(.text+0x55): undefined reference to `my_ex(std::string)'
collect2: error: ld returned 1 exit status

If I add braces around the conversion, like this:

const char * text = "exception text";
std::cout << "Creating MyException object using std::string(const char *)." << std::endl;
MyException my_ex((std::string(text)));
std::cout << "MyException object created." << std::endl;
throw my_ex;

Then it works as I would have expected:

 $ g++ main.cpp
 $ ./a.out
Creating MyException object using std::string(const char *).
MyException constructor, s1: exception text
MyException object created.
terminate called after throwing an instance of 'MyException'
Aborted (core dumped)

I have the following questions:

  1. Why does my first example compile? How come I don't get a compilation error?
  2. Why doesn't the code compile, when I try to throw my_ex;?
  3. Why do the braces resolve the problem?
Dowery answered 23/11, 2016 at 14:54 Comment(0)
N
34

According to most vexing parse, MyException my_ex(std::string(text)); is a function declaration; the function is named my_ex, taking a parameter named text with type std::string, returns MyException. It's not an object definition at all, then no constructor will be called.

Note the error message undefined reference to 'my_ex(std::string)' for throw my_ex; (you're trying to throw a function pointer in fact), which means that can't find the definition of the function my_ex.

To fix it you can add additional parentheses (as you has shown) or use braces which supported from C++11:

MyException my_ex1((std::string(text)));
MyException my_ex2{std::string(text)};
MyException my_ex3{std::string{text}};
Neufer answered 23/11, 2016 at 15:1 Comment(10)
This is probably the most infuriating quirk of C++.Spivey
@GillBates Especially in such a complex scenario.Neufer
Thank you, I did not know this syntax, hence my bafflement at the situation.Aeri
because of this very quirk I always use brace initialization when possible.Changchangaris
MyException my_ex(static_cast<std::string>(text)) also works (live example).Overdrive
MyException ex{ {text} }.Fourposter
@black Just MyException ex{text} is fine. You don't need the second set of braces.Solipsism
throw MyException(text);Turgor
Why does not my_ex2 creation require extra parentheses, though it also calls the same constructor?Gist
@SeshadriR Because braces can't be used for the parameter list, so no ambiguous grammar issue for it.Neufer
F
4

The answer is to use {} (braced-init) as much as possible. Sometimes, though, it might be missed inadvertently. Luckily, compilers (like clang, under no extra warning flags) can hint:

warning: parentheses were disambiguated as a function declaration [-Wvexing-parse]
    MyException my_ex(std::string(text));
                     ^~~~~~~~~~~~~~~~~~~
test.cpp:13:23: note: add a pair of parentheses to declare a variable
    MyException my_ex(std::string(text));
                      ^
                      (                )
1 warning generated.

which will immediately point you the issue.

Fourposter answered 23/11, 2016 at 19:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.