I am having some trouble with figuring out why I cannot seem to get std::vector::erase work with a vector of my own class objects. The following code throws a "No viable overloaded '='" error, and cannot figure out why after some extensive searching overflow/tutorialspoint/...
my class definition 'MyClass.hpp':
#include <string>
#include <vector>
class node;
class graph{
public:
graph();
std::vector<node> nodeList;
};
class node{
public:
node(std::string name) : name(name){};
void operator=(node& rhs);
std::string name;
std::vector<node> adjacent;
};
void node::operator=(node& rhs){
name = rhs.name;
adjacent = rhs.adjacent;
}
and my main file:
#include "MyClass.hpp"
int main(int argc, const char * argv[]) {
node node1("root"), node2("leaf");
node1.adjacent.push_back(node2);
node2.adjacent.push_back(node1);
node1.adjacent.erase(node1.adjacent.begin());
graph myGraph;
return 0;
}
std::vector::erase adds the requirement that the type T of the vector's elements must be move assignable. This means in particular that an expression like t = rv must have a return type of T & (reference to T) and a value of t (so the returned reference must reference the assigned to object).
Your Node::operator= has return type void, thus violates above requirement. Further, the usual (and probably most reasonable) type signatures for copy / move assignment operators are as follows:
T & operator=(T &&); // move assignment
T & operator=(T); // copy assignment, pass by value
T & operator=(T const &); // copy assignment, pass by const reference
But, instead of fixing your operator, you should discard it all together! Your Node class does (should) not deal with ownership (the std::vector is doing this for you), thus per the rule of zero you should not provide a custom copy or move assignment operators (nor a destructor).
Related
I have a class Foo with no sensible default constructor. I would also prefer to keep the copy-assignment operator private, although that may become impossible. (I'd like to make the class “almost” immutable, whence thread-safe, by having const fields and the small number of mutators that cast const way as private and early in the object lifetime.)
Creating std::vector<Foo> under these constraints is a little bit of a challenge. I came up with a solution I haven't seen elsewhere (see, for example, earlier SO question 1). I have a custom iterator which, when dereferenced, creates a Foo. It is set up such that each invocation increments to the next value of Foo in the vector. The sequence is easy to define. I define operator++, next, advance, distance and operator* on CustomIterator.
Then I have
std::vector<Foo> foo_vec{CustomIterator(0), CustomIterator(size_of_vector)};
No access issues. No unnecessary constructions. No copies. Anyone see a problem with this?
I will summarize the comments. The simple factory generates vector of initialized elements.
#include <vector>
class X {
explicit X(int value) : value_(value) {}
X& operator=(const X&) = default;
friend std::vector<X> generate(int from, int to);
public:
const int value_;
};
// simplest factory ever
std::vector<X> generate(int from, int to) {
std::vector<X> result;
result.reserve(to - from);
for (int k = from; k < to; ++k) {
result.emplace_back(std::move(X(k)));
}
return std::vector<X>();
}
int main() {
auto v = generate(0, 10);
static_cast<void>(v);
}
I want to have an automatic conversion from std::string into my type my_type, defined as
typedef std::pair<std::string,int> my_type;
such that the .first part of the converted my_type is the string and the .second part is always 0.
It should also work if one calls the function std::string fun(my_type x, ...) { return x.first; } with, say,
std::string s = "Hello"; fun(s, ...);.
I don't want to define a new class instead of my_type and also not to overload all of my functions if possible. I tried to wrap my head around how to use operator for this but I can't get my program to compile.
EDIT:
Since this doesn't seem to be possible without defining a custom struct or so, here is a workaround that I came up with, but I was hoping it can be achieved without defining a new class/struct. Thank you for saving me more time trying to do this, though.
class Element {
public:
Element() {};
Element(std::string s, int a) { name = s; value = a; };
Element(std::string s) { name = s; value = 0; };
...
std::string return_name() { return name; };
private:
std::string name;
int value;
};
std::string fun(Element x) { return x.return_name(); };
Calling std::string s = "Hello"; fun(s); works now automatically.
It is not possible to add new implicit conversions for existing classes, such as std::pair. Implicit conversions can only be member functions:
A non-explicit constructor that can be called with one argument. If there are more arguments, they must have default values.
operator T() const conversion operator.
And it is not possible to add new member functions to classes without changing class definition. This restriction is in place to prevent a function introduced at global or namespace scope from changing the semantics of your existing code.
What you can do instead is create a new class with a conversion constructor (a non-explicit constructor that can be called with one argument):
struct MyPair : std::pair<std::string, int> {
// In this class scope pair now refers to std::pair<std::string, int>.
MyPair(std::string const& a)
: pair(a, 0)
{}
MyPair(pair const& a)
: pair(a)
{}
};
The derivation from std::pair<std::string, int> makes it possible to pass MyPair where std::pair<std::string, int> is expected. And another constructor for converting std::pair<std::string, int> to MyPair.
first I want to say that using references to Value in this example is a must.
So,I have a tree (for simplicity, this is tree with nodes that can only have one child, this code is not my original assignment, but the problem is the same) and I want to store values in it.
To export the values, i have to use vector of std::pairs, with key and reference to value.
I'm trying to do that in a recursive function 'col', to push_back the values one after another.
My problem is, the final values are different. When i switched references to pointers, it worked properly. But as I said, it has to be references.
I don't understand, whats the difference between those two in this case? They both should point to a memory on a heap and that address should stay the same, right?
Here is the code:
#include <memory>
#include <vector>
#include <iostream>
template <typename Value>
class Tree {
public:
class Node {
std::unique_ptr<Value> value;
std::unique_ptr<Node> child;
public:
friend Tree;
Node(const Value i) : value(std::make_unique<Value>(i)) {}
Value* getvalue() { return value.get();}
Node* getchild() { return child.get();}
};
const std::vector<std::pair<std::string,const Value&>> collect() {
std::vector<std::pair<std::string,const Value&>> list;
col(list, root.get());
return list;
}
void col(std::vector<std::pair<std::string,const Value&>>& list, Node* node) {
list.push_back(std::make_pair("k", *node->getvalue()));
if (node->getchild() != nullptr) {
col(list, node->getchild());
}
}
void addNode(const Value i) {
add(root.get(), i);
}
Node* getroot() { return root.get();}
private:
std::unique_ptr<Node> root = std::make_unique<Node>(0);
void add(Node* node, const Value& i) {
if (node->getchild() == nullptr) {
node->child = std::make_unique<Node>(i);
} else {
add(node->getchild(), i);
}
}
};
int main() {
Tree <int>t;
t.addNode(1);
t.addNode(2);
t.addNode(3);
auto a = t.collect();
for (auto p : a) {
std::cout << p.first << " " << p.second << "\n";
}
}
The output is:
k 0
k -424282688
k -424282688
k 0
(and is different after each call)
list.push_back(std::make_pair("k", *node->getvalue()));
std::make_pair deduces the template arguments for std::pair from its function arguments, and never uses reference types for them (note the std::decay part on the linked page). So the return type of make_pair is std::pair<const char*, Value>, instantiated as std::pair<const char*, int>. The int second; member of this pair is a copy of *node->getvalue().
vector<T>::push_back(T&&) requires an argument of the actual element type, which here is T = std::pair<std::string, const int&>. There's an implicit conversion from std::pair<const char*, int> to std::pair<std::string, const int&>: the std::string first; member is constructed from the raw string pointer, and the const int& second; member is bound to the member of the input pair.
But the std::pair<const char*, int> was a temporary, so as soon as the statement ends, the lifetime of the copied and referenced value is over. Next time you try to use the reference, bang.
Instead of using make_pair, specify the exact type you need:
list.push_back(std::pair<std::string, const Value&>("k", *node->getvalue()));
or
list.push_back(decltype(list)::value_type("k", *node->getvalue()));
or put a using OutPairType = std::pair<std::string, const Value&>; in the class definition and use that instead of typing it out elsewhere.
Also note that when a std::pair has reference members, like the default behavior of any struct with reference members, the operator= copy and move assignment operators will copy or move the referenced objects. They will not (and cannot) change the reference member to refer to the same object as the right hand side's member, like a pointer assignment would. And std::vector sometimes uses operator= of its value_type (though not in push_back). You might want to consider using std::pair<std::string,std::reference_wrapper<const Value>> instead.
See the code below - I am trying to put a const object into a vector. I know the answer is "STL containers require objects to be assignable and copy constructable", but, without citing the standard, can anyone explain what the problem with doing this is? I don't understand why a class like this could not be copied (besides that c++ doesn't allow it).
All it is is a value stored that is not allowed to be changed - why can't putting it in a vector simply create another one of these objects?
#include <vector>
// Attempt 1
// /home/doriad/Test/Test.cxx:3:8: error: non-static const member ‘const int MyClass::x’, can’t use default assignment operator
// struct MyClass
// {
// int const x;
// MyClass(int x): x(x) {}
// };
//
// int main()
// {
// std::vector<MyClass> vec;
// vec.push_back(MyClass(3));
// return 0;
// }
// Attempt 2
// /home/doriad/Test/Test.cxx:28:23: error: assignment of read-only member ‘MyClass::x’
struct MyClass
{
int const x;
MyClass(int x): x(x) {}
MyClass& operator= (const MyClass& other)
{
if (this != &other)
{
this->x = other.x;
}
return *this;
}
};
int main()
{
std::vector<MyClass> vec;
vec.push_back(MyClass(3));
return 0;
}
EDIT:
It is possible to do this with std::set and std::list. I guess it is the sort() function in std::vector that uses assignment. This is not UB right?
#include <set>
// Attempt 1
struct MyClass
{
int const x;
MyClass(int x): x(x) {}
bool operator< (const MyClass &other) const;
};
bool MyClass::operator<(const MyClass &other) const
{
if(this->x < other.x)
{
return true;
}
else if (other.x < this->x)
{
return false;
}
}
int main()
{
std::set<MyClass> container;
container.insert(MyClass(3));
return 0;
}
EDIT2: (Removing a bunch of stuff that doesn't have to work) The C++11 standard states that the insert method for vector and deque (and the default implementation of push_back for that matter) requires the value type to be CopyAssignable, i.e., the value supports:
t= v;
Classes and structs with const members are not CopyAssignable by default, so what you want to do won't work.
This doc (n3173) has an explanation for the various container requirements.
One possible solution would be to store pointers to the objects in the vector, because pointers are assignable and copy constructable.
Another possible solution would be to declare x without the const keyword, but ensure that it cannot be modified through encapsulation (i.e. you should declare it as private and don't modify from anywhere outside the constructor)..
When you place an object of type MyClass in the std::vector, the vector will make a copy of the object for storage, and not the object you passed to it.
#include <list>
#include <set>
#include <iterator>
#include <algorithm>
using namespace std;
class MyContainer {
public:
string value;
MyContainer& operator=(const string& s) {
this->value = s;
return *this;
}
};
int main()
{
list<string> strings;
strings.push_back("0");
strings.push_back("1");
strings.push_back("2");
set<MyContainer> containers;
copy(strings.begin(), strings.end(), inserter(containers, containers.end()));
}
The preceeding code does not compile. In standard C++ fashion the error output is verbose and difficult to understand. The key part seems to be this...
/usr/include/c++/4.4/bits/stl_algobase.h:313: error: no match for ‘operator=’ in ‘__result.std::insert_iterator::operator* [with _Container = std::set, std::allocator >]() = __first.std::_List_iterator::operator* [with _Tp = std::basic_string, std::allocator >]()’
...which I interpet to mean that the assignment operator needed is not defined. I took a look at the source code for insert_iterator and noted that it has overloaded the assignment operator. The copy algorithm must uses the insert iterators overloaded assignment operator to do its work(?).
I guess that because my input iterator is on a container of strings and my output iterator is on a container of MyContainers that the overloaded insert_iterator assignment operator can no longer work.
This is my best guess, but I am probably wrong.
So, why exactly does this not work and how can I accomplish what I am trying to do?
What would work would be using the constructor (which would make more sense instead of the assignment):
class MyContainer {
public:
string value;
MyContainer(const string& s): value(s) {
}
};
Then the second problem is that set also requires its contents to be comparable.
As to the cause, insert_iterator works by overloading operator=:
insert_iterator<Container>& operator= (typename Container::const_reference value);
As you can see, the righthand value must be either the value type of the container or implicitly convertible to it, which is exactly what a (non-explicit) constructor achieves and the assignment operator doesn't.
Technically you could also make it work without changing the class (e.g if you don't want an non-explicit constructor) by providing a suitable conversion function:
MyContainer from_string(const std::string& s)
{
MyContainer m;
m = s; //or any other method how to turn a string into MyContainer
return m;
}
which can be used with std::transform:
transform(strings.begin(), strings.end(), inserter(containers, containers.end()), from_string);
You need to add:
1. Constructor that takes string (you are trying to add string to container that can contain MyContainer objects).
2. bool operator < (set uses it by default to compare elements)
For instance :
class MyContainer
{
public:
MyContainer(const string& v):value(v){};
};
bool operator <(const MyContainer &c1, const MyContainer &c2)
{
return c1.value <c2.value;
}
The problem is twofold:
You're trying to fill a set of MyContainer objects
... from a list of string objects.
The copy() algorithm tries to convert each string object to a MyContainer object. In C++ to add to class MyContainer conversion support from type string to type MyContainer you need to add a constructor that takes a parameter of type string:
struct MyContainer {
MyContainer(const string& s) : value(s) { }
bool operator<(const MyContainer& o) const { return value < o.value; }
private:
string s;
};
You don't need an assignment operator, because the compiler can get the copying done by the copy-constructor: convert a string to a MyContainer and then use the default assignment operator to assign one MyContainer object onto the other. You will, however need an operator<() because C++ sets are sorted.