【问题标题】:Defining a function for different containers为不同的容器定义一个函数
【发布时间】:2020-11-25 01:07:08
【问题描述】:

我想定义一个接受不同类型容器的函数:拥有数据的容器(如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);
}

我对@9​​87654325@ 的第一个实现运行良好,因为它接受了两种迭代器。

但是,由于boost::iterator_range 不拥有数据,它可以是const(而数据是非常量),所以我想允许右值作为参数。这意味着我需要第二个实现。

这是唯一的方法吗?有没有办法避免第二次实现?

【问题讨论】:

  • 如果你想允许右值作为输入,那么你应该有一个将右值引用作为参数的重载。在任何情况下,在显示的两个版本中,v 都应被视为 const 引用。
  • 是的,我只是想知道是否有更好的方法来实现重载以避免代码重复。另外,据我了解,只有 const 参考实现不适用于 std::vector。
  • 我说你应该传递 v 参数,而不是 c 参数,因为 v 在所有情况下都是只读参数。
  • 对不起,我误会了。我现在明白了,感谢您的评论,但这确实超出了我的问题的重点。
  • 对于c 参数,由于它是模板化的,请查看"universal/forwarding references",它可以将左值或右值作为输入,具体取决于调用者。

标签: c++ boost iterator


【解决方案1】:

你可以使用完美转发:

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

这实际上并不是“完美转发”,因为您不希望利用移动语义,如果您愿意,您应该写 std::forward&lt;C&gt;(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);
}

¹ 请注意,如果这样做,请注意不要在移动后使用。

【讨论】:

  • 谢谢。正如@Remy Lebeau 所建议的那样,我阅读了有关通用参考的更多信息。他们几乎处理了所有案例,除了我添加的“案例 4”。即,对于“通用参考”,fill 使用 std::vector&lt;int&gt;(10) 作为参数。之前,我通过将 r-values 设为 const 来防止这种情况发生(然后迭代器是常量)。有没有办法结合这两种方法?即,让函数接受“左值”还是“常量右值”?
  • PS: 刚刚意识到我使用fill_impl 执行此操作,该fill_impl 采用通用引用并由上述fill 的两个重载调用(具有左值和常量右值)。只是想知道,出于教育目的,是否有一些模板魔法可以解决这个问题。
  • 是的,这是我的看法,只是为了验证您是否有相同的想法:coliru.stacked-crooked.com/a/29552be3abdb5ce2 - 即使没有尝试分配,您也可以使用 static_assert 使其失败:coliru.stacked-crooked.com/a/57ff76fbb44df62d
  • 我的元编程经验法则:显式是好的。你越是追求含蓄的魔法,你就会遇到越痛苦的惊喜。如果一切都是显式调度,那么它“总是”很容易解决
  • 其实摆脱dispatch的是trivial。我喜欢这个解决方案,假设它是正确的:)
【解决方案2】:

您可以删除*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
}

See it live

【讨论】:

  • 谢谢!这是一种有趣的方法,但很难以多个容器作为参数进行扩展。
【解决方案3】:

供我(和其他人(?))将来参考。我找到了以下解决方案来勾选我所有的框:接受左值和右值,确保右值是恒定的,也不需要调度函数。

#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);
}

【讨论】:

    猜你喜欢
    • 2021-03-07
    • 1970-01-01
    • 2015-06-18
    • 2018-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-10
    • 2018-06-23
    相关资源
    最近更新 更多