Is constructible?

Today I want to share with you something that really surprised me. Currently, Tomasz Kamiński and Ville Voutilainen are working on fixing a certain issue with std::optional’s converting constructors (which deserves a separate post). At some point, in the solution, they perform the following type-trait test:

is_constructible<T, U>::value || is_convertible<U, T>::value

If the varying order of T and U upsets you, rest assured that this is correct. This is how these traits are defined: is_constructible takes the created type first, whereas is_convertible takes the converted-to type second. But what really struck me here is the apparent redundancy. When type U is convertible to T does it not imply that T is constructible from U? Or in other words, if the following copy-initialization works:

T v = u;

The following direct-initialization:

T v (u);

should also work? Well, this is C++. It turns out that such expectation is not necessarily correct.

Most of developers probably never has to deal with such nuances, but when you design a generic library you want to consider every tiny corner case. We have one here. First, we need to grasp the difference between these two kinds of initialization:

T v1 = u; // copy-initialization
T v2 (u); // direct-initialization

A common-sense answer would be “the latter is more powerful because it additionally considers explicit constructors and conversion operators.” True, it considers more constructors, but to ‘consider’ an additional constructor does not necessarily mean ‘successfully use it construction’. Consider the following type definition of a class that tries to represent rational numbers:

class Q
  int _num, _den;
  explicit Q(double r, Rounding round = Rounding::to_nearest);
  // more ...

  Q(int num, int den = 1) : _num(num), _den(den) {}
  // more ...

It is generally a good practice to make every (non-copy, non-move) constructor explicit, unless you really want the constructor to participate in conversions; and this is what we do here. Integer number 2 is a valid unambiguous and intuitive representation of a rational number; and we want it converted to Q where needed. We also have another special and potentially dangerous constructor that we use internally, and definitely do not want to expose it in the interface, and definitely do not want it to participate in conversions.

The result, however, is quite surprising:

Q q1 = 1.0; // compiles
Q q2 (1.0); // compiler-error

Of course, you might question that anyone would store an arbitrary double as a rational number, but the following example looks more plausible:

double get_number();

Q q1 = std::floor(get_number());
Q q2 (std::floor(get_number()));

Here, std::floor is making sure that a double is storing an integral number.

Now, to explain what happens here. When we use copy-initialization, the first constructor is not visible because it is declared as explicit, so there is only one constructor to choose from, and it is suitable after the implicit conversion from double to int has been applied.

In direct initialization we need to consider both constructors. And the first one is a better match because it requires no additional conversion from double to int. The fact that it is declared private does not affect how the overload resolution works. Only later, after we have decided which overload will be used, is the access checking performed: and at that point it causes a compiler error.

This shows one important thing: declaring something private doesn’t mean it is an implementation detail; it may affect the type’s interface. Accessing a private function is not the only situation that causes a type to be convertible from but not constructible from another type. Another such situation is when we declare an explicit constructor as deleted.

struct R
  explicit R(double, double = 1.0) = delete;
  R(int num, int den = 1);

How much contrived such classes are? You probably don’t see them very often. All those examples have the same issue: they declare competing constructors. The second example with class R tries to implement the advice described here. But apparently that advice should be augmented with the following precaution: when you declare a constructor deleted in order to make another constructor less ‘greedy’, the former has to be as explicit as the latter.

There are other implications of the set of constructors as in our class Q. All the ‘factory’ functions will not be able to work with it:

auto p = std::make_shared<Q>(1.0);  // doesn't work
std::shared_ptr<Q> q {new Q(1.0)};  // doesn't work
boost::optional<Q> o;
o.emplace(1.0);                     // doesn't work

So, I would say it is not a good idea to have a type T convertible form some type U but not constructible from U. Good or bad, such types are allowed to exist, and generic libraries like the future std::optional need to handle them, so we might expect a new trait saying “anyhow create-able from U”; especially that it is possible to construct a T form a U even when both is_constructible<T, U>::value and is_convertible<U, T>::value return false:

struct X
    int i;

int main()
  static_assert(!std::is_constructible<X, int>::value &&
                !std::is_convertible<int, X>::value, "");
  X x {1};  
This entry was posted in programming and tagged , , . Bookmark the permalink.

9 Responses to Is constructible?

  1. Anatoliy says:

    Everything new is well forgotten old. Once upon a time you’ve answered my very the same question here .

    • 🙂 Indeed; I quite forgot that. FWIW, the solution to the SO problem was “constructible but not convertible”, which is quite ordinary; in this post it is “convertible but not constructible”, which I claim to be at least unusual. Now, that I think of it, I can see that we also have situations when T is neither constructible, nor convertible from U, but you can still initialize T from U using the direct-brace-initialization:

      struct X
          int i;
      int main()
        static_assert(!std::is_constructible<X, int>::value &&
                      !std::is_convertible<int, X>::value, "");
        X x {1};  
  2. Krzysztof says:

    > It is generally a good practice to make every (non-copy, non-move) constructor explicit

    I would change it to “every effectively single-argument”. Does `explicit` have special meaning in other constructor-related cases?

  3. nikitablack says:

    > When we use copy-initialization, the first constructor is not visible because it is declared as explicit.
    Why? Can you please tell more about this or give some references?

    • That is the primary purpose of keyword explicit: it is intended to mean “do not use this constructor to (implicitly) convert from one type to another”. For online references, I can refer you to

      For the references in the Standard, see [class.conv.ctor], which says: “A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor. […] An explicit constructor constructs objects just like non-explicit constructors, but does so only where the direct-initialization syntax or where casts are explicitly used.”

      And [over.match.copy], which says: “Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a user-defined conversion can be invoked to convert an initializer expression to the type of the object being initialized.Overload resolution is used to select the user-defined conversion to be invoked. [ …] Assuming that “cv1 T” is the type of the
      object being initialized, with T a class type, the candidate functions are selected as follows: […] When the type of the initializer expression is a class type “cv S”, the non-explicit conversion functions of S and its base classes are considered. […]”.

  4. Nkt says:

    Great post, thanks a lot🙂

  5. Morten says:

    ‘void’ is funny because it can be constructed from anything and is convertible to nothing😉

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s