Consider the following code of std::pair
default constructor from the STL implementation shipped with Microsoft Visual Studio 15.4.5:
template<class _Uty1 = _Ty1,
class _Uty2 = _Ty2,
class = enable_if_t<is_default_constructible<_Uty1>::value
&& is_default_constructible<_Uty2>::value>>
constexpr pair()
: first(), second()
{ // default construct
}
I set /std:c++latest
option, so, according to the standard (I use the draft n4659 here) I expect that this constructor will be excluded from the overload resolution if either _Ty1
or _Ty1
is not default constructible:
23.4.2 Class template pair [pairs.pair]
EXPLICIT constexpr pair();
Effects: Value-initializes first and second.
Remarks: This constructor shall not participate in overload resolution unless
is_default_constructible_v<first_type>
is true andis_default_constructible_v<second_type>
is true. [ Note: This behavior can be implemented by a constructor template with default template arguments.]
In the implementation above the exclusion is performed as follows:
class = enable_if_t<is_default_constructible<_Uty1>::value
&& is_default_constructible<_Uty2>::value>
As far as I know, SFINAE does not work for template type parameters default values.
Interestingly enough, in Microsoft Visual Studio 15.5.3 the constructor has been changed to the "right version" ("right" based on my limited template knowledge):
template<class _Uty1 = _Ty1,
class _Uty2 = _Ty2,
enable_if_t<conjunction_v<
is_default_constructible<_Uty1>,
is_default_constructible<_Uty2>
>, int> = 0>
constexpr pair()
: first(), second()
{ // default construct
}
So I am wondering whether the first implementation is correct, and, if it is correct, what is the point of changing it to the second one.
It's not that SFINAE doesn't work inside default template arguments; it's that they do not count as part of the signature and so putting your SFINAE machinery there means that you have to make the signature different in some other way if you want to build an overload set.
Thus, this is fine:
template<class T, class=std::enable_if_t<std::is_integral_v<T>>>
T meow();
template<class T, class=std::enable_if_t<!std::is_integral_v<T>>>
void meow();
because the signatures are different (return type is part of the signature of function templates - but not functions); so is this:
template<class T, class=std::enable_if_t<std::is_integral_v<T>>>
void meow(T);
template<class T, class=std::enable_if_t<!std::is_integral_v<T>>>
void meow(const T&);
but this isn't (it redeclares the same function template and so tries to give the same template parameter a default template argument twice):
template<class T, class=std::enable_if_t<std::is_integral_v<T>>>
void meow(const T&);
template<class T, class=std::enable_if_t<!std::is_integral_v<T>>>
void meow(const T&);
With respect to that pair
constructor template in particular, you can't really tell if it's correct without knowing what the other constructor templates are. That said, I'd be greatly surprised if they got it wrong; any problem should be easily catchable with simple unit tests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With