3

According to the C++ Core Guidelines, rule C.46:

By default, declare single-argument constructors explicit.

The reason is to avoid unintended conversions.

However, why is the explicit keyword recommended to avoid unintended conversions only for constructors with a single argument? Wouldn't implicit conversions with multiple arguments also be potentially problematic?

EDIT:

For example, in the code below, can't ComplexC and ComplexD be used for implicit conversion (see function calls below)? If so, wouldn't the rule apply to ComplexD as well?

class ComplexA {            // example from C.46        
public:
    ComplexA(double r) {}
};

class ComplexB {
public:
    explicit ComplexB(double r) {}
};

class ComplexC {
public:
    ComplexC(double r, double i) {}
};

class ComplexD {
public:
    explicit ComplexD(double r, double i) {}
};

void f_non_explicit(ComplexA a) {
    std::cout << "Called f_non_explicit for ComplexA\n" ; }

void f_explicit(ComplexB b) {
    std::cout << "Called f_explicit for ComplexB\n" ; }

void f_non_explicit(ComplexC c) {
    std::cout << "Called f_non_explicit for ComplexC\n" ; }

void f_explicit(ComplexD d) {
    std::cout << "Called f_explicit for ComplexD\n" ; }

int main()
{
    ComplexA za = {10.7};
    // ComplexB zb = {10.7};        // error: constructor is explicit in copy-initialization

    ComplexC zc = {10.7, 11};
    // ComplexD zd = {10.7, 11};     // error: constructor is explicit in copy-initialization

    f_non_explicit({100.1});            // prints "Called f_non_explicit for ComplexA"
    f_non_explicit({200.1, 300.1});     // prints "Called f_non_explicit for ComplexC"

    // f_explicit({100.1});             // error: error: no matching function
    // f_explicit({200.1, 300.1});      // error: error: no matching function
}
9
  • 12
    There is no such thing as an implicit conversion with multiple arguments. Commented Jun 16 at 15:02
  • It's easier to understand if you take the assignment operator instead, as the same is valid for it: You can't assign multiple values to a variable or object. It's the same for conversion constructors, they can only be used to convert from a single value. Commented Jun 16 at 15:08
  • 1
    In C++, it's really easy to get an unintended implicit single-argument conversion construction of an object, but much harder (not impossible, though) to get an unintended implicit multi-argument conversion construction an object. Generally, a multi-argument conversion construction will be intended, rather than a surprise. Commented Jun 16 at 17:12
  • 2
    The primary reason to use explicit with a multi-argument constructor is to prevent unintended implicit conversions when using initializer lists. As several C++ luminaries have said, "C++: all the defaults are wrong." In my opinion, the explicit should have been the default behavior if not specified, and implicit should have been required to opt-in for converting constructor. Alas, that ship has sailed. Commented Jun 16 at 17:22
  • 1
    @Eljay, thanks, corrected. Commented Jun 16 at 18:46

1 Answer 1

11

The usage you initially showed in your main is not what this core guideline is for. The guideline is to avoid situations where the fact that there is a conversion is hidden to the user. For instance:

Let's say we have this function that takes a Foo parameter.

void some_function(Foo f);

And then there are calls to some_function. The idea is to be obvious when there are conversions when calling it, even without looking at its signature:

{
    Bar b;    
    // ..
    some_function(b); // <- implicit hidden conversion; not desired

    some_function(Foo{b}); // <- explicit conversion, OK
}

This confusion doesn't occur with multiple arguments:

{
    Bar bar;
    Baz baz
    // ..
    some_function({bar, baz}); // <- obvious there's a new object created here
                               // you can still force it with explicit constructor
                               // to be `some_function(Foo{bar, baz})` if you want
                               // but it's ok to leave it implicit also
}
Sign up to request clarification or add additional context in comments.

5 Comments

thank you for your answer. Isn't there a hidden conversion in the line f_non_explicit({200.1, 300.1}); of the edited question above?
there is no conversion. It's a construction. And it's not hidden. It's obvious that a new object is constructed. What's hidden is the type of the object constructed. Depending on the type this might be ok or not.
some_function({b}); // forbidden too, even if obvious there's a new object created here though.
True. But you cannot inhibit some_function(b) while allowing some_function({b}), so it makes sense to disallow both, instead of allowing both as the some_function(b) is the problematic one we are trying to avoid. My two cents anyway.
@bolov, if Foo has two constructor: Foo(const Bar& bar) and Foo(const Bar& bar, const Baz& baz), why would you say that there is an implicit conversion in Bar bar; some_function(bar); but not in Bar bar; Baz baz;some_function({bar, baz});? Whatever the answer, wouldn't the advice "declare explicit" apply for both constructors?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.