问题
This looks to me like a basic problem so apologies in advance for duplicate posts. Not sure what the terminology is though.
I have a class, say my_data
, for storing numbers. It has the basic constructors etc. and it would be handy to support elementary operations such as adding to the elements, e.g. increasing the values in the data.
This is the most elementary way to describe the 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);
}
The problem starts when I want to add the values of one object to the values of another (abusing the fact here that they are the same size to omit checks):
$ 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}’
The code of adding two my_data
objects together does not seem to be used. Of course <typename U>
can be objects of type X
as well as my_data<X>
but how do I let the compiler know when to use the second version of add()
?
My current version of the program uses
void add (const bisArray<U> addition, const std::true_type&)
for adding an element and
void add (const bisArray<U> addition, const std::false_type&)
for adding a my_data
object (the calls are a.add(2, std::is_arithmetic<int>::type() );
for adding an element and a.add(a, std::is_arithmetic<my_data<int>>::type() );
for adding a my_data
object).
It is not really a solution though because there is nothing to prevent the occurrence of the calls a.add( a , std::is_arithmetic<int>::type() );
and a.add( 2 , std::is_arithmetic<my_data<int>>::type() );
which lead to a segmentation fault.
Is there a mechanism to resolve this more elegantly?
回答1:
There are a number of issues with your code. The most immediate reason for your code not compiling was already pointed out by Igor Tandetnik in the comments.
I'll start in “declaration order” which is not the order of severity:
- The correct header to
#include
forstd::size_t
is notstdlib.h
butcstddef
. (Generally, don't#include
the*.h
but thec*
versions.) - You probably want to add a default constructor that initializes your type with an empty vector.
- You probably want to make the constructors that take one argumrnt
explicit
. It avoids bad surprises when an integer suddenly converts to amy_data
. - The constructor that takes a
std::vector<T>
needlessly makes a copy of its argument. At the very least, you shouldstd::move
it into its destination. You might also want to consider providing an overload forconst std::vector<T>&
(which does a copy) andstd::vector<T>&&
(which does a move). - Prefer initializer lists over assignment in the constructor.
- Generally prefer C++11's uniform initialization syntax (
{ … }
). - Use C++11 lambdas instead of the legacy binders. It's more readable and at least as efficient.
- Consider overloading operators
+=
and<<
instead of providing named member functions. - Your vector addition needs a size-check.
std::transform
cannot tell how long the second and third ranges are. - The addition functions shouldn't make a needless copy of their argument.
- In the vector addition's
std::transform
call, a binary function should be provided. Since you have templated the function, neitherstd::plus<T>
norstd::plus<U>
are applicable. Use a lambda that adds aT
and aU
. This will also be more efficient if, for exampleT = std::string
andU = char *
.
Let's put all this together:
#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;
}
Output:
1, 2, 3,
3, 4, 5,
6, 8, 10,
One more thing that you might wish to address is the superfluous trailing comma in the output.
来源:https://stackoverflow.com/questions/28745566/implementing-scalar-and-vector-addition-for-custom-type-with-stdtransform