C++: Compilation error with explicit keyword

480 Views Asked by At

The following code throws compilation error:

#include <stdio.h>

class Option
{
    Option() { printf("Option()\n"); };
public:
    explicit Option(const Option& other)
    {
        printf("Option(const)\n");
        *this = other;
    }

    explicit Option(Option& other)
    {
        printf("Option(non-const)\n");
        *this = other;
    }

    explicit Option(const int&)
    {
        printf("Option(value)\n");
    }
};

void foo(Option someval) {};

int main()
{
    int val = 1;
    Option x(val);
    foo(x);
}

The error thrown is:

main.cpp:31:10: error: no matching function for call to ‘Option::Option(Option&)’
     foo(x);
          ^
main.cpp:5:5: note: candidate: ‘Option::Option()’
     Option() { printf("Option()\n"); };
     ^~~~~~
main.cpp:5:5: note:   candidate expects 0 arguments, 1 provided
main.cpp:25:6: note:   initializing argument 1 of ‘void foo(Option)’
 void foo(Option someval) 

The error goes away if I remove the explicit keyword from explicit Option(const Option& other)

Can someone explain to me what is the cause of compilation error? Also, if there a difference between explicit Option(const Option& other) and explicit Option(Option& other)?

1

There are 1 best solutions below

2
On

In the call foo(x), a new Option has to be created, which will become someVal during the execution of foo's body. That is, x needs to be copied into someVal. The compiler essentially tries to initialize Option someVal(x); (trying first Option(Option&), then Option(Option const&)), but it cannot, because you said that both of those constructors are explicit and should not be implicitly called. With C++17, you can explicitly insert the missing constructor call to make it work: foo(Option(x)). Before C++17, it is impossible to call foo, because the compiler will keep trying to insert calls to a constructor to Option but none are available for insertion.

In the language of the standard, a function call like foo(x) calls for the parameter someVal to be copy-initialized from x. Copy-initializing an object of a certain class from an object of that class or a derived class considers only the converting constructors of that destination class. "Converting constructor" is just a fancy name for "constructor that isn't explicit". The best constructor out of these is then selected by normal overload resolution. Since none of your constructors are not explicit, this always fails and foo is uncallable before C++17. Since C++17, when the argument is a prvalue (as in foo(Option(x))), the requirement to call a constructor can be sidestepped and foo becomes callable.

To your side question:

Also, if there a difference between explicit Option(const Option& other) and explicit Option(Option& other)?

Of course: the first one promises it will not modify its argument and the second one doesn't. You already know that they can be defined to do different things, and overload resolution will prefer one over the other, depending on context:

Option x(1);
Option const y(2);
Option a(x); // calls Option(Option&) if available, which may modify x; calls Option(Option const&) if not, which shouldn't modify x
Option b(y); // must call Option(Option const&) because that promises not to modify y; cannot call Option(Option&) because it may modify y