【问题标题】:Getting UB in my variadic template summing a variable number of vectors element-wise在我的可变参数模板中获取 UB,按元素对可变数量的向量求和
【发布时间】:2021-02-07 10:25:06
【问题描述】:

我正在尝试创建一个函数AddVector,它可以明智地添加(可变)数量的向量元素。我想我明白了,但是当我得到错误的输出时,我显然没有。我正在添加三个双精度向量,每个大小为 5,包含 1+2+1,因此我期望

4 4 4 4 4

我明白了

1.36234e-316 2.0326e-316 4 4 4 

这显然是错误的(可能是未初始化的内存?)

我使用 CppInsights 查看了翻译后的代码,但这似乎还可以。我在这里做错了什么?

我的代码:

#include <vector>
template<typename T>
using Vec = std::vector<T>;

template<typename T>
auto SumAndIncVcIt(T& t) {
    return *t++;
}

template<typename T, typename... Args>
auto SumAndIncVcIt(T& t, Args&... args) {
    return *t++ + SumAndIncVcIt(args...);
}

#include <tuple>
template<typename... Args>
auto VcBegins(Args... args){
    return std::make_tuple(cbegin(args)...);
}

template<typename T, size_t... Is>
auto SumAndIncVcIts_impl(T& t, std::index_sequence<Is...>) {
    return SumAndIncVcIt(std::get<Is>(t)...);
}

template<class Tuple>
auto SumAndIncVcIts(Tuple& t) {
    return SumAndIncVcIts_impl(t,
        std::make_index_sequence<std::tuple_size<Tuple>{}>{}
    );
}

template<typename T, typename... Args>
Vec<T> AddVector(Vec<T> const& vt, Vec<Args> const&...  vargs){
    auto vret = Vec<T>(size(vt));
    auto vcIts = VcBegins(vt, vargs...);
    auto retIt = begin(vret);
    while(retIt != end(vret)){
        *retIt++ = SumAndIncVcIts(vcIts);
    }
    return vret;
}

#include<iostream>

int main() {
    constexpr auto size = 5;
    Vec<double> a(size, 1.0), b(size, 2.0);
    auto c = AddVector(a, b, a);

    for(auto const& el : c){
        std::cout << el << " ";
    }
}

附言是的,我应该使用 SFINEA 或概念。

【问题讨论】:

    标签: c++ c++17 variadic-templates undefined-behavior


    【解决方案1】:

    不确定你的问题出在哪里,你自己找到了:) ...但这就是我用 C++17 和大小实现它的方式:

    #include <vector>
    #include <algorithm>
    
    template<typename T>
    using Vec = std::vector<T>;
    
    template<typename T, typename...Args>
    Vec<T> AddVector_impl(Vec<Args> const & ... vecs){
        auto sizes = {vecs.size()...};
        auto new_size = *std::min_element(sizes.begin(),sizes.end());
    
        Vec<T> res(new_size);
    
        for(std::size_t i=0;i<new_size;++i){
            res[i]=(vecs[i]+...);
        }
        return res;
    }
    
    // Ensures at least one vector, also sets its return type.
    template<typename T, typename... Args>
    Vec<T> AddVector(Vec<T> const& vt, Vec<Args> const&...  vargs){
        return AddVector_impl<T>(vt,vargs...);
    }
    
    #include <iostream>
    int main() {
        constexpr auto size = 5;
        Vec<double> a(size, 1.0), b(size, 2.0);
        auto c = AddVector(a, b, a);
    
        for(auto const& el : c){
            std::cout << el << " ";
        }
    }
    

    Live Godbolt demo

    输出:

    4 4 4 4 4 
    

    仅迭代器解决方案

    #include <vector>
    #include <algorithm>
    
    template<typename T>
    using Vec = std::vector<T>;
    
    
    template<typename T, typename...Args>
    Vec<T> AddVector_impl(Vec<Args> const & ... vecs){
        auto its = std::tuple(vecs.cbegin()...);
    
        auto add_inc = [](auto&... iters){
            return ((*iters++) + ... );
        };
        auto end_check = [&](auto&...iters){
            return ((iters!=vecs.cend()) && ...);
        };
        Vec<T> res;
        auto it = std::back_insert_iterator(res);
        while(std::apply(end_check,its)){
            *it++=std::apply(add_inc,its);
        }
        return res;
    }
    
    template<typename T, typename... Args>
    Vec<T> AddVector(Vec<T> const& vt, Vec<Args> const&...  vargs){
        return AddVector_impl<T>(vt,vargs...);
    }
    
    #include <iostream>
    int main() {
        constexpr auto size = 5;
        Vec<double> a(size, 1.0), b(size, 2.0);
        auto c = AddVector(a, b, a);
    
        for(auto const& el : c){
            std::cout << el << " ";
        }
    }
    

    Live Godbolt demo.

    【讨论】:

    • 谢谢。我发现了我的错误。我在 VcBegins... 无效的迭代器中传递值。无论如何,我会研究你的解决方案。我想使用迭代器,制作类似 STL 的算法。
    • @JHBonarius 好吧,是的,迭代器让这变得相当复杂。但是您仍然使用 size 函数,它不适用于迭代器。
    • 真的。但如果接口是AddVector(cbegin(v1), cend(v1), cbegin(v2), cbegin(v3), ...),我可以使用std::distance。会有一个大问题是向量的长度不相等......我真的很感谢你的回答;)也许我应该在 CodeReview 上发布这个
    • p.s.,+0 的原因是什么?对于没有采用 int 的 operator+ 重载的任何类型 T,它将失败。
    • @JHBonarius 我发布了一个仅使用迭代器的解决方案,我重用了你的元组想法:)
    【解决方案2】:

    自己找到的...

    template<typename... Args>
    auto VcBegins(Args... args){
        return std::make_tuple(cbegin(args)...);
    }
    

    按值传递,因此向量被复制,我得到无效的迭代器

    当然应该是

    template<typename... Args>
    auto VcBegins(Args const&... args){
        return std::make_tuple(cbegin(args)...);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多