Reputation: 2480
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?
Upvotes: 0
Views: 213
Reputation: 25409
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:
#include
for std::size_t
is not stdlib.h
but cstddef
. (Generally, don't #include
the *.h
but the c*
versions.)explicit
. It avoids bad surprises when an integer suddenly converts to a my_data
.std::vector<T>
needlessly makes a copy of its argument. At the very least, you should std::move
it into its destination. You might also want to consider providing an overload for const std::vector<T>&
(which does a copy) and std::vector<T>&&
(which does a move).{ … }
).+=
and <<
instead of providing named member functions.std::transform
cannot tell how long the second and third ranges are.std::transform
call, a binary function should be provided. Since you have templated the function, neither std::plus<T>
nor std::plus<U>
are applicable. Use a lambda that adds a T
and a U
. This will also be more efficient if, for example T = std::string
and U = 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.
Upvotes: 1