vendredi 25 août 2017

C++: SFINAE to differentiate fill and range constructors?

Here is a question asking about how to differentiate fill and range constructors. The code is copied here:

template <typename T>
struct NaiveVector {
    vector<T> v;
    NaiveVector(size_t num, const T &val) : v(num, val) { // fill
        cout << "(int num, const T &val)" << endl;
    }

    template <typename InputIterator>
    NaiveVector(InputIterator first, InputIterator last) : v(first, last) { // range
        cout << "(InputIterator first, InputIterator last)" << endl;
    }
};

The code above doesn't work, as described in that question. The solution is using SFINAE to define the range constructor, like this example:

template
<
    typename InputIterator
,   typename = typename ::std::enable_if
    <
        ::std::is_same
        <
            T &
        ,   typename ::std::remove_const
            <
                decltype(*(::std::declval< InputIterator >()))
            >::type
        >::value
    ,  void
    >::type
>
NaiveVector(InputIterator first, InputIterator last) : v(first, last)
{
    cout << "(InputIterator first, InputIterator last)" << endl;
}

This gist of this solution is comparing the dereferenced type of InputIterator with T &. If it is truly an iterator, the comparison of std::is_same will be true, and the range constructor will be selected; if it is not an iterator, there will be a template substitution failure so the range constructor will be deleted, and hence the fill constructor will be selected.

However, there is a problem for the solution above. If the input InputIterator is of a const_iterator type (e.g. cbegin()), then dereferencing it will yield a const T &, and its const-ness cannot be removed by std::remove_const, so the comparison in std::is_same will be false, resulting in the range constructor being wrongly deleted.

GNU's C++ STL had a bug possibly (I guess) because of this. In this bug, the method only accept an iterator, but not a const_iterator.

Question:

(1) is there a nicer workaround than combining two std::is_same conditions with an OR operator, one comparing the dereferenced type with T &, the other with const T &?

(2) if the workaround described in (1) is adopted, is std::remove_const still necessary, now that it cannot remove const-ness from a reference type, and dereferencing an (const or non-const) iterator will always yield a reference, either const T & or T &.

Aucun commentaire:

Enregistrer un commentaire