Wednesday, July 20, 2011

Conversions of std::pair<T,U>s

I decided to start writing a few months ago, but I never actually committed to it. Then I decided a few days ago that I was actually going to do it, and I have spent the last few days trying to decide what would be a good first post. I am still undecided. But today I read an interesting question in StackOverflow, and decided to write about it.

The problem Rafał has is with implicit conversions. In his application he has two types that are convertible, but only explicitly convertible. He is surprised that an std::pair<>that contains one of those types is implicitly convertible to a std::pair<> of that contains the second type.

The problem

Given two types A and B, such that an explicit conversion from A to B is valid, but an implicit conversion is not, why is std::pair<A,A> implicitly convertible to std::pair<B,B>?

Well, the answer to this type of question is usually simple: the properties of the types used to instantiate a template do not propagate to the template itself. That is the reason why you cannot pass a std::vector< derived* > to a function that requires a std::vector< base* >, even if you can pass a pointer to derived to a function that takes a pointer to base.

The only operation in std::pair that allows for conversions is a constructor template. The implementation of that template in the STL shipped with gcc uses the initialization-list to initialize the members of the pair. Because that is explicit initialization, the compiler gladly accepts the code.

But, is this the case?

No, not really. In this case the standard is by Rafał's side. The description of the behavior for that particular conversion constructor is stated in §20.2.2:

template <typename U, typename V>
pair( const pair<U,V> & p );


Initializes members from the corresponding members of the argument, performing implicit conversions as needed.

The standard seems quite clear in stating that implicit conversions are to be used, so this seems like a bug in the compiler side not adhering to the standard. As always, the interesting question is the why.

Can we implement that constructor template with the semantics defined in the standard?

We cannot use implicit conversions in the constructor without adding additional requirements to the instantiating types (for example, using default construction and assignment of the arguments). But if our goal is just to abide the standard and reject all calls to that template when an implicit conversion is not available, that is easy.

Detecting implicit convertibility is simple, and we can add a static assert to the constructor. The con of the approach is that while this will inhibit implicit conversions when the pair's elements are not implicitly convertible, it does not allow for explicit conversions of the pairs. But that is probably fine. Or can we do better?

3 comments:

  1. third paragraph should read "why is std::pair < A, A > implicitly convertible to std::pair < B, B > ?" - nice post though! (I messed up with the brackets)

    ReplyDelete
  2. Thanks for the heads up with respect to the typo. I am actually surprised that *anyone* has read this... I thought I had at least a few weeks where I could play with the tool and learn how to write, post, format code...

    ReplyDelete
  3. David Could you please find some time and continue your blogs? its really helpful to learn from you. Maybe when you answer a question at stack overflow you can extend those answers here(if possible) or pick out a new topic.This is a request since i can understand clearly when you explain. cheers:-).

    ReplyDelete