Boost logo

Boost :

Subject: Re: [boost] Boost 1.61.0 Release Candidate 1
From: Stephan T. Lavavej (stl_at_[hidden])
Date: 2016-05-06 16:38:51


[Frank Mori Hess]
> There is a testcase3.cpp attached to ticket 12110
> https://svn.boost.org/trac/boost/ticket/12110
> You have to uncomment the foo call.

Ok, this one's different from the repros I saw earlier.

Here, VS 2015 Update 2's implementation is correctly implementing the C++17 Working Paper N4582. What's happening is that you have a DoubleHolder with an unconstrained constructor DoubleHolder(const ValueType&). (It really should be constrained to exist only when its body would compile.) Then you construct a tuple<DoubleHolder>. Finally, you pass it to foo() which (after template argument deduction) takes a tuple<DoubleHolder> by value.

What is INTENDED is copy construction, from the modifiable lvalue x. There are two other relevant constructors, though. The converting copy constructor turns out to be a red herring - it doesn't participate because of LWG 2549's PR, and even if it did, the plain copy ctor would be preferred (const tuple& beats const tuple<U>&). The one that wins is the perfect forwarding ctor.

The change here is that the perfect forwarding ctor is (sometimes) implicit in C++17, to support brace-returning tuples. In this case, it is indeed implicit because DoubleHolder's constructor is implicit. So, it has to be considered when constructing foo()'s parameter from x in main(). This perfect forwarding constructor participates because DoubleHolder claims to be able to accept a tuple<DoubleHolder> (that's the unconstrained bit previously mentioned). And the perfect forwarding ctor wins because it takes (tuple<DoubleHolder>&), which is an exact match and better than the copy ctor's (const tuple<DoubleHolder>&).

This is similar to the other bug report that I mentioned, except that your DoubleHolder avoids additional weirdness caused by the UDT constructor taking by value. The root cause is the same: tuple's perfect forwarding ctor can out-compete the copy ctor.

I've been informed that libc++, while having recently implemented the WP Standardese, is immune to this issue because they implemented a (non-Standard) constraint to prevent the perfect forwarding ctor from outcompeting here. And I've been informed that libstdc++'s implementation of the WP is equally affected like VC's, although I'm not sure how new of a version (perhaps trunk) is required.

I am strongly considering applying libc++'s strategy to VC for Update 3. In general, I am wary of doing anything not mandated by the Standard, especially in something as complicated as tuple, but the disambiguation should be simple to implement and very targeted (I cannot imagine a reason where the perfect forwarding ctor should ever be allowed to outcompete the copy/move ctor).

In the meantime, I thought of an additional workaround for Boost. Instead of using 2-tuples which are immune, if you detect when this scenario is about to happen (i.e. trying to copy this kind of 1-tuple), if you manually get<0> the element within, that should avoid the problem of giving whole tuples to types like DoubleHolder.

Thanks,
STL


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk