使用 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>
可以是 X
和 my_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 在评论中已经指出了您的代码未编译的最直接原因。
我将从“声明顺序”开始,这不是严重性的顺序:
- 对于
std::size_t
,正确的 header 到 #include
不是 stdlib.h
,而是 cstddef
。 (一般来说,不要 #include
*.h
而是 c*
版本。)
- 您可能想要添加一个默认构造函数,用空向量初始化您的类型。
- 您可能希望构造函数接受一个参数
explicit
。当一个整数突然转换为 my_data
. 时,它避免了糟糕的意外
- 采用
std::vector<T>
的构造函数不必要地复制了它的参数。至少,你应该 std::move
它进入它的目的地。您可能还想考虑为 const std::vector<T>&
(进行复制)和 std::vector<T>&&
(进行移动)提供重载。
- 在构造函数中优先使用初始化列表而不是赋值。
- 通常更喜欢 C++11 的统一初始化语法 (
{ … }
)。
- 使用 C++11 lambda 代替传统的绑定器。它更具可读性,至少同样高效。
- 考虑重载运算符
+=
和 <<
而不是提供命名成员函数。
- 你的向量加法需要 size-check。
std::transform
无法判断第二个和第三个范围有多长。
- 加法函数不应对其参数进行不必要的复制。
- 在矢量加法的
std::transform
调用中,应提供二元函数。由于您已将函数模板化,因此 std::plus<T>
和 std::plus<U>
都不适用。使用添加 T
和 U
的 lambda。这也会更有效,例如 T = std::string
和 U = 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,
您可能希望解决的另一件事是输出中多余的尾随逗号。
这在我看来是一个基本问题,因此对于重复的帖子提前表示歉意。虽然不确定术语是什么。
我有一个 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>
可以是 X
和 my_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 在评论中已经指出了您的代码未编译的最直接原因。
我将从“声明顺序”开始,这不是严重性的顺序:
- 对于
std::size_t
,正确的 header 到#include
不是stdlib.h
,而是cstddef
。 (一般来说,不要#include
*.h
而是c*
版本。) - 您可能想要添加一个默认构造函数,用空向量初始化您的类型。
- 您可能希望构造函数接受一个参数
explicit
。当一个整数突然转换为my_data
. 时,它避免了糟糕的意外
- 采用
std::vector<T>
的构造函数不必要地复制了它的参数。至少,你应该std::move
它进入它的目的地。您可能还想考虑为const std::vector<T>&
(进行复制)和std::vector<T>&&
(进行移动)提供重载。 - 在构造函数中优先使用初始化列表而不是赋值。
- 通常更喜欢 C++11 的统一初始化语法 (
{ … }
)。 - 使用 C++11 lambda 代替传统的绑定器。它更具可读性,至少同样高效。
- 考虑重载运算符
+=
和<<
而不是提供命名成员函数。 - 你的向量加法需要 size-check。
std::transform
无法判断第二个和第三个范围有多长。 - 加法函数不应对其参数进行不必要的复制。
- 在矢量加法的
std::transform
调用中,应提供二元函数。由于您已将函数模板化,因此std::plus<T>
和std::plus<U>
都不适用。使用添加T
和U
的 lambda。这也会更有效,例如T = std::string
和U = 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,
您可能希望解决的另一件事是输出中多余的尾随逗号。