как использовать SFINAE для возврата (const_) итератора из метода end () - PullRequest
1 голос
/ 18 октября 2019

Я хотел создать оболочку коллекции, в которой метод end был бы открыт для члена. Сама коллекция может быть или не быть const, поэтому я не могу различить const_iterator и iterator на ее основе, но внутренняя коллекция (шаблон) определяет постоянство. Я думал, что использование enable_if<is_const<T сделает это, но, похоже, нет. Спасибо за вашу помощь

#include <cassert>
#include <type_traits>
#include <vector>
#include <algorithm>

template <typename ITEMS>
struct collection {
    ITEMS& _items;

    collection(ITEMS& items) : _items(items) {
    }

    auto find(int i) const {
        return std::find(_items.begin(), _items.end(), i);
    }

    typename std::enable_if_t<std::is_const<ITEMS>::value>::const_iterator
    end() const {
        return _items.end();
    }

    typename std::enable_if_t<!std::is_const<ITEMS>::value>::iterator
    end() const {
        return _items.end();
    }
};

template <typename ITEMS>
collection<ITEMS>
make_collection(ITEMS& items) {
    return collection<ITEMS>(items);
}

int main() {
    std::vector<int> ints = {1, 2, 3};
    auto col = make_collection(ints);                                                                                                
    const auto it = col.find(3);                                                                                                     
    assert(it != col.end());                                                                                                         

    const auto cints = ints;                                                                                                         
    auto ccol = make_collection(cints);                                                                                              
    const auto cit = ccol.find(3);                                                                                                   
    assert(cit != ccol.end());                                                                                                       

    return 0;                                                                                                                        
}    

edit: использование только auto end() const { return _items.end(); } работает, но я хотел бы понять, почему волшебство шаблонов не работает.

1 Ответ

1 голос
/ 18 октября 2019

Есть 2 проблемы с вашим кодом. Во-первых, std::enable_if_t будет либо недействительным, либо разрешит тип. Если вы не укажете, какой тип по умолчанию void. В ваших примерах вы не указываете тип.

Вторая проблема заключается в том, что функции-члены зависят от шаблона класса. Весь код внутри шаблона должен быть действительным при его создании. Когда вы создаете экземпляр collection с контейнером cont, неконстантная перегрузка не сможет определить тип возвращаемого значения. Но он все еще является частью шаблона класса, и если его часть потерпит неудачу, компилятор не проигнорирует эту часть, но даст ошибку компиляции.

Способ решить эту проблему - сделать1008 * методы шаблонных методов и перенос параметра шаблона из класса. Таким образом, метод end не устанавливается до тех пор, пока не будет вызван, и если один из них завершится неудачно, он будет в порядке, пока работает другой.

template <typename ITEMS>
struct collection {
    ITEMS& _items;

    collection(ITEMS& items) : _items(items) {
    }

    auto find(int i) const {
        return std::find(_items.begin(), _items.end(), i);
    }

    template <typename T = ITEMS>
    typename std::enable_if_t<std::is_const<T>::value, T>::const_iterator
    end() const {
        return _items.end();
    }

    template <typename T = ITEMS>
    typename std::enable_if_t<!std::is_const<T>::value, T>::iterator
    end() const {
        return _items.end();
    }
};
...