Определение функции для разных контейнеров - PullRequest
1 голос
/ 05 августа 2020

Я хочу определить функцию, которая принимает различные типы контейнеров: те, которые владеют данными (например, std::vector), и те, которые не владеют данными (например, boost::iterator_range).

Я написал следующее :

#include <vector>
#include <boost/range/iterator_range.hpp>

template<typename C>
void fill(C& c, typename C::value_type v){
    for (auto& j : c)
        j = v;
}


template<typename C>  // How to avoid this implementation?
void fill(const C& c, typename C::value_type v){
    for (auto& j : c)
        j = v;
}

int main(){
    std::vector<int> v(10);
    auto rng = boost::make_iterator_range(v.begin(), v.end());


    fill(v, 1);  // Case 1 -- Calling on std::vector
    fill(rng, 2); // Case 2 -- Calling on boost::iterator_range

    // Case 3 -- Calling with an r-value, requires the overload
    fill(boost::make_iterator_range(v.begin(), v.end()), 3);

    // Case 4 -- Should not be allowed
    // fill(std::vector<int>(10), 4);
}

Моя первая реализация fill работает хорошо, поскольку принимает оба вида итераторов.

Однако, поскольку boost::iterator_range не владеет данными, это может быть const (пока данные не являются константными), поэтому я хотел бы разрешить rvalues ​​в качестве аргументов. Это означает, что мне нужна вторая реализация.

Это единственный способ сделать это? Есть ли способ избежать второй реализации?

Ответы [ 3 ]

1 голос
/ 05 августа 2020

Вы можете удалить случай, когда *c.begin() является константной ссылкой.

template<typename C>
void fill_impl(C& c, typename C::value_type v, std::false_type){
    for (auto& j : c)
        j = v;
}

template<typename C>
void fill_impl(C& c, typename C::value_type v, std::true_type) = delete;

template<typename C>
void fill(const C& c, typename C::value_type v){
    fill_impl(c, v, std::is_const<std::remove_reference_t<decltype(*c.begin())>>{});
}

template<typename C>
void fill(C& c, typename C::value_type v){
    fill_impl(c, v, std::is_const<std::remove_reference_t<decltype(*c.begin())>>{});
    // hopefully always true_type, but you never know
}

Посмотреть вживую

1 голос
/ 05 августа 2020

Вы можете использовать идеальную пересылку:

template <typename C>
void fill(C&& c, typename std::decay_t<C>::value_type v) {
    for (auto& j : c)
        j = v;
}

На самом деле это не «идеальная пересылка», поскольку вы не хотите использовать семантику перемещения, если хотите, вам следует написать std::forward<C>(c) вместо c ¹

Live On Coliru

#include <boost/range/iterator_range.hpp>
#include <vector>

template <typename C>
void fill(C&& c, typename std::decay_t<C>::value_type v) {
    for (auto& j : c)
        j = v;
}

int main() {
    std::vector<int> v(10);
    auto rng = boost::make_iterator_range(v.begin(), v.end());

    fill(v, 1);   // Case 1 -- Calling on std::vector
    fill(rng, 2); // Case 2 -- Calling on boost::iterator_range

    // Case 3 -- Calling with an l-value, requires the overload
    fill(boost::make_iterator_range(v.begin(), v.end()), 3);
}

¹ Обратите внимание, что вы должны быть осторожны, чтобы не использовать последующий ход, если вы это сделаете.

0 голосов
/ 05 августа 2020

Для моей (и других (?)) Справки в будущем. Я нашел следующее решение, чтобы отметить все свои поля: Принимает lvalues ​​и rvalues, обеспечивая постоянство rvalue, также без необходимости в функциях отправки.

#include <vector>
#include <boost/range/iterator_range.hpp>
#include <type_traits>

template <typename T>
using const_if_rvalue = typename std::conditional<std::is_rvalue_reference<T&&>::value,
                                                  const std::decay_t<T>, std::decay_t<T>>;

template<typename C>
void fill(C&& cr, typename std::decay_t<C>::value_type const& v){
    typename const_if_rvalue<C>::type& c = cr;
    for (auto& j : c)
        j = v;
}


int main(){
    typedef std::vector<int> array;
    typedef boost::iterator_range<array::iterator> range;
    array v(10);
    range rng = boost::make_iterator_range(v.begin(), v.end());

    fill(v, 1);  // Case 1 -- Calling on std::vector
    fill(rng, 2); // Case 2 -- Calling on the range

    // Case 3 -- Calling with an l-value, requires the overload
    fill(boost::make_iterator_range(v.begin(), v.end()), 3);

    // Case 4 -- Should not be allowed
    // fill(std::vector<int>(10), 4);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...