Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC std::pair implementation: is SFINAE applied correctly here?

Tags:

c++

templates

stl

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 and is_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.

like image 534
Edgar Rokjān Avatar asked Mar 08 '23 03:03

Edgar Rokjān


1 Answers

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.

like image 61
T.C. Avatar answered Apr 27 '23 11:04

T.C.