使用 std::transform 为自定义类型实现标量和向量加法

Implementing Scalar and Vector Addition for Custom Type with std::transform

这在我看来是一个基本问题,因此对于重复的帖子提前表示歉意。虽然不确定术语是什么。

我有一个 class,比如 my_data,用于存储数字。它具有基本的构造函数等,并且可以方便地支持基本操作,例如添加到元素,例如增加数据中的值。

这是最基本的描述方式 class:

#include <stdlib.h>
#include <vector>
#include <iostream>     
#include <algorithm>     
#include <numeric>      
#include <iterator>     

template <typename T>

class my_data {

public:
  my_data  (const my_data& other);
  my_data& operator=(const my_data& rhs);
  my_data  (const size_t bsize) { my_datavec.resize(bsize); }
  my_data  (std::vector<T> init) { my_datavec = init; }  

  template <typename U>
  void add(const U addition) {
    std::transform(my_datavec.begin(), my_datavec.end(), 
                   my_datavec.begin(), 
                   bind2nd(std::plus<T>(), addition) );
  }

  template <typename U>
  void add(const my_data<U> addition) {
     std::transform(my_datavec.begin(), my_datavec.end(), 
                    addition.my_datavec.begin(),
                    my_datavec.begin(), 
                    std::plus<T>() );
  }  

  void print() {    
    std::ostream_iterator<T> out_it (std::cout,", ");
    std::copy(my_datavec.begin(), my_datavec.end(), out_it);
  }

protected:
  std::vector<T> my_datavec;

};

int main() {

  std::vector<int> int_test({1,2,3});
  my_data<int> a(int_test);

  a.add(2);
  a.print();

  a.add(a);
  a.print();

  return(0);

}

当我想将一个对象的值添加到另一个对象的值时,问题就开始了(滥用它们大小相同的事实来省略检查):

$ g++ -std=c++11 template2.cpp -o template2
In file included from /usr/include/c++/4.7/bits/stl_function.h:741:0,
             from /usr/include/c++/4.7/string:50,
             from /usr/include/c++/4.7/bits/locale_classes.h:42,
             from /usr/include/c++/4.7/bits/ios_base.h:43,
             from /usr/include/c++/4.7/ios:43,
             from /usr/include/c++/4.7/ostream:40,
             from /usr/include/c++/4.7/iostream:40,
             from template2.cpp:3:
/usr/include/c++/4.7/backward/binders.h: In instantiation of ‘std::binder2nd<_Operation> std::bind2nd(const _Operation&, const _Tp&) [with _Operation = std::plus<int>; _Tp = my_data<int>]’:
template2.cpp:20:5:   required from ‘void my_data<T>::add(U) [with U = my_data<int>; T = int]’
template2.cpp:45:10:   required from here
/usr/include/c++/4.7/backward/binders.h:170:57: error: invalid cast from type ‘const my_data<int>’ to type ‘_Arg2_type {aka int}’

两个my_data对象相加的代码好像没有用到。当然 <typename U> 可以是 Xmy_data<X> 类型的对象,但是我如何让编译器知道何时使用 add() 的第二个版本?

我当前的程序版本使用

void add (const bisArray<U> addition, const std::true_type&) 

用于添加元素和

void add (const bisArray<U> addition, const std::false_type&) 

用于添加 my_data 对象(调用是 a.add(2, std::is_arithmetic<int>::type() ); 用于添加元素和 a.add(a, std::is_arithmetic<my_data<int>>::type() ); 用于添加 my_data 对象)。

虽然这不是一个真正的解决方案,因为没有什么可以阻止调用 a.add( a , std::is_arithmetic<int>::type() );a.add( 2 , std::is_arithmetic<my_data<int>>::type() ); 的发生,这会导致分段错误。

有没有一种机制可以更优雅地解决这个问题?

您的代码存在许多问题。 Igor Tandetnik 在评论中已经指出了您的代码未编译的最直接原因。

我将从“声明顺序”开始,这不是严重性的顺序:

  1. 对于 std::size_t,正确的 header 到 #include 不是 stdlib.h,而是 cstddef。 (一般来说,不要 #include *.h 而是 c* 版本。)
  2. 您可能想要添加一个默认构造函数,用空向量初始化您的类型。
  3. 您可能希望构造函数接受一个参数 explicit。当一个整数突然转换为 my_data.
  4. 时,它避免了糟糕的意外
  5. 采用 std::vector<T> 的构造函数不必要地复制了它的参数。至少,你应该 std::move 它进入它的目的地。您可能还想考虑为 const std::vector<T>&(进行复制)和 std::vector<T>&&(进行移动)提供重载。
  6. 在构造函数中优先使用初始化列表而不是赋值。
  7. 通常更喜欢 C++11 的统一初始化语法 ({ … })。
  8. 使用 C++11 lambda 代替传统的绑定器。它更具可读性,至少同样高效。
  9. 考虑重载运算符 +=<< 而不是提供命名成员函数。
  10. 你的向量加法需要 size-check。 std::transform 无法判断第二个和第三个范围有多长。
  11. 加法函数不应对其参数进行不必要的复制。
  12. 在矢量加法的 std::transform 调用中,应提供二元函数。由于您已将函数模板化,因此 std::plus<T>std::plus<U> 都不适用。使用添加 TU 的 lambda。这也会更有效,例如 T = std::stringU = char *.

让我们把所有这些放在一起:

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <iterator>
#include <stdexcept>
#include <vector>

template<typename T>
class my_data
{

public:

  my_data()
  {
  }

  explicit my_data (const std::size_t size)
  {
    data.resize(size);
  }

  explicit my_data(const std::vector<T>& init) : data {init}
  {
  }

  explicit my_data(std::vector<T>&& init) : data {std::move(init)}
  {
  }

  template<typename U>
  void
  operator+=(const U& a)
  {
    std::transform(data.begin(),
                   data.end(),
                   data.begin(),
                   [&a](const T& b){ return a + b; });
  }

  template<typename U>
  void
  operator+=(const my_data<U>& other)
  {
    if (other.data.size() != this->data.size())
      throw std::invalid_argument {"incompatible sizes"};
    std::transform(data.begin(),
                   data.end(),
                   other.data.begin(),
                   data.begin(),
                   [](const T& a, const U& b){ return a + b; });
  }

  friend
  std::ostream&
  operator<<(std::ostream& os, const my_data& md)
  {
    std::ostream_iterator<T> outit {os, ", "};
    std::copy(md.data.begin(), md.data.end(), outit);
    return os;
  }

protected:

  std::vector<T> data {};
};

int main() {
  std::vector<int> inttest {1, 2, 3};
  my_data<int> a {inttest};
  std::cout << a << std::endl;
  a += 2;
  std::cout << a << std::endl;
  a += a;
  std::cout << a << std::endl;
}

输出:

1, 2, 3, 
3, 4, 5, 
6, 8, 10, 

您可能希望解决的另一件事是输出中多余的尾随逗号。