Implicit constructor argument conversion in C++11

2020-04-11 10:59发布

问题:

Lets concider following code:

class A{
public:
  A(int x){}
};

class B{
public:
  B(A a){};
};


int main() {
  B b = 5;
  return 0;
}

And while compiling the compiler complains that:

/home/test/main.cpp:80: candidate constructor not viable: no known conversion from 'int' to 'A' for 1st argument

I don't want to create B(int) constructor - I would love the compiler to implicit convert this int to A object.

EDIT:

Direct initialisation works like this:

B b(5);

Is it possible to use the assigment operator instead?

回答1:

Just to be clear:

B b = 5;

is "copy initialisation" not assignment. (See http://www.gotw.ca/gotw/036.htm).

In this case, you are asking the compiler to perform two implicit user-defined conversions first, i.e. int -> A, A -> B before a temporary B object is passed to the copy constructor for B b. The compiler is allowed to elide the temporary object but semantically you are still asking the language to make two jumps across types.

All implicit behaviour in programming languages is inherently scary. For the sake of a little syntactic sugar, we are asking c++ to "do some magic to make it just work". Unexpected type conversions can wreck havoc in large complex programmes. Otherwise, every time you wrote a new function or a new class, you would have to worry about all the other types and functions it could affect, with the side -effects rippling across your code. Would you really want implicit conversions from int -> apple -> horse -> horse_power -> aeroplane?

For that reason, c++ only allows a single implicit user-defined conversion:

12.3 Conversions [class.conv]

1 Type conversions of class objects can be specified by constructors and by conversion functions. These conversions are called user-defined conversions and are used for implicit type conversions (clause 4), for initialization (8.5), and for explicit type conversions (5.4, 5.2.9).

4 At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.

You are better off either with an explicit cast or "direct initialisation" both of which make it clear to the compiler and collaborators exactly what you are trying to do. Either the traditional or the new uniform initialisation syntax works:

B b(5);
B b{5};
B b = {5};


回答2:

Use direct initialisation instead:

B b(5);


回答3:

You can use a converting constructor that is constrained on conversions to A.

class B {
public:
    // It doesn't hurt to keep that one
    B(A a){};

    template<
        typename T
        , EnableIf<std::is_convertible<T, A>>...
    >
    B(T&& value)
    {
        // How to use the value
        A a = std::forward<T>(value);
    }
};

// Now B b = foo; is valid iff A a = foo; is, except for a few exceptions

Make sure you understand the purpose of the EnableIf constraint. If you do not use it, you will face gnarly compilation errors, or worse: no errors at all. You should also carefully consider if making B convertible from potentially lots of types is at all worth it. Implicit conversions tend to make a program harder to understand, and that can quickly outweigh the apparent benefits they give.