I'm new to C++ and very confused on how to approach this. In Javascript, I can do something like this to access an object dynamically very easily:
function someItem(prop) {
const item = {
prop1: 'hey',
prop2: 'hello'
};
return item[prop];
}
In C++, I'm assuming I have to use a Struct, but after that I'm stuck on how to access the struct member variables dynamically.
void SomeItem(Property Prop)
{
struct Item
{
Proper Prop1;
Proper Prop2;
};
// Item[Prop] ??
}
This could be terrible code but I'm very confused on how to approach this.
This is a simple example of how to create an instance of a struct and then access its members:
#include <iostream>
#include <string>
struct Item {
std::string prop1 = "hey";
std::string prop2 = "hello";
};
int main() {
Item myItem;
std::cout << myItem.prop1 << std::endl; // This prints "hey"
std::cout << myItem.prop2 << std::endl; // This prints "hello"
return 0;
}
As mentioned in the comments, it looks like you might want a map. A map has keys and values associated with them, as an example you could have a key "prop1" be associated with a value "hey":
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, std::string> myMap;
myMap["prop1"] = "hey";
myMap["prop2"] = "hello";
std::cout << myMap["prop1"] << std::endl; // This print "hey"
std::cout << myMap["prop2"] << std::endl; // This print "hello"
return 0;
}
The first would be considered "normal" struct usage in C++ and the other is more applicable to cases where you have to look things up by keys
As mentioned in a comment, in C++ you would not define a custom structure for this, but rather use a std::unordered_map. I don't know Javascript, though if Property is an enum (it could be something else with small modifications) and return item[prop]; is supposed to return a string, then this might be close:
#include <string>
#include <unordered_map>
#include <iostream>
enum class Property { prop1,prop2};
std::string someItem(Property p){
const std::unordered_map<Property,std::string> item{
{Property::prop1,"hey"},
{Property::prop2,"hello"}
};
auto it = item.find(p);
if (it == item.end()) throw "unknown prop";
return it->second;
}
int main(){
std::cout << someItem(Property::prop1);
}
std::unordered_map does have a operator[] that you could use like so return item[p];, but it inserts an element into the map when none is found for the given key. This is not always desirable, and not possible when the map is const.
Related
I started learning C++ and I made a simple thing like printing variables etc, but I wanted to make a new value on a variable like in Python:
test = "hello world"
print(test)
test = 5
print(test + 6)
So I had this:
string test = "hello world";
cout << test << "\n";
And now I wanted to assign a number to test, so I used int test = 5;, but I got an error:
redefinition of 'test' with a different type
Is it possible to assign a new type to a variable somehow?
is it possible to assign a new type to a variable somehow?
A new type, no. C++ is a statically typed language. Variable types are specified at compile-time and cannot change at runtime.
For what you are asking, the closest thing available is std::variant in C++17 and later, which is a fixed class type that can hold different kinds of values at runtime, eg:
#include <iostream>
#include <string>
#include <variant>
using myVariant = std::variant<int, std::string>;
void print(const myVariant &v)
{
std::visit([](const auto &x) { std::cout << x; }, v);
std::cout << "\n";
}
int main()
{
myVariant test;
test = "hello world";
print(test);
test = 5;
print(test);
return 0;
}
Online Demo
In C++17 and newer, std::variant is a type that can be used to store multiple different types of data. Though you'll have to do a bit of finagling with visitors to get the polymorphic print you know from Python.
#include <variant>
#include <string>
#include <iostream>
struct PrintVisitor {
template <typename T>
void operator()(const T& arg) {
std::cout << arg << std::endl;
}
};
int main() {
std::variant<std::string, int> test = "hello world";
std::visit(PrintVisitor(), test);
test = 5;
std::visit(PrintVisitor(), test);
}
I have a class that has a constructor. I now need to make a map with it as a value how do I do this? Right now without a constructor I do.
#include <iostream>
#include <map>
using namespace std;
class testclass {
public:
int x = 1;
};
int main()
{
map<int,testclass> thismap;
testclass &x = thismap[2];
}
If I added a constructor with arguments how would I add them to the map? I basically need to do
#include <iostream>
#include <map>
using namespace std;
class testclass {
public:
int x = 1;
testclass(int arg) {
x = arg;
}
};
int main()
{
map<int,testclass> thismap;
testclass &x = thismap[2];
}
This obviously wouldn't work since it requires an argument but I can't figure a way of doing this.
This is how you can add items of your own class to your map.
Note : I used a string in testclass to better show difference
between key and value/class.
#include <iostream>
#include <string>
#include <map>
class testclass
{
public:
explicit testclass(const std::string& name) :
m_name{ name }
{
};
const std::string& name() const
{
return m_name;
}
private:
std::string m_name;
};
int main()
{
std::map<int, testclass> mymap;
// emplace will call constructor of testclass with "one", and "two"
// and efficiently place the newly constructed object in the map
mymap.emplace(1, "one");
mymap.emplace(2, "two");
std::cout << mymap.at(1).name() << std::endl;
std::cout << mymap.at(2).name() << std::endl;
}
Using std::map::operator[] requires that the mapped type is default-constructible, since it must be able to construct an element if one doesn't already exist.
If your mapped type is not default-constructible, you can add elements with std::map::emplace, but you still can't use std::map::operator[] to search, you will need to use std::map::find() or so.
That's a rather obvious feature of std::map (and very similar other std containers). Some of their operations require specific type requirements for good reasons.
There is no problem to create such a map as you suggest in the first place, however, you are restricted to method calls that do not require potential default construction. The operator[] is such a method, since in the case the element is not found, it is created. That is what does not work in your example. Just use other methods with little impact on the map usage and you can still succeed:
#include <iostream>
#include <map>
using namespace std;
class testclass {
public:
int x = 1;
testclass(int arg) {
x = arg;
}
};
int main()
{
map<int,testclass> thismap;
thismap.insert( {2, testclass(5)} );
auto element2 = thismap.find(2);
if (element2 != thismap.end()) {
testclass& thiselement = element2->second;
cout << "element 2 found in map, value=" << thiselement.x << endl;
}
auto element5 = thismap.find(5);
if (element5 == thismap.end()) {
cout << "no element with key 5 in thismap. Error handling." << endl;
}
}
Main issue: avoid operator[].
Note:
Looking at the other very good answers, there are a lot of methods that can be used without default construction. There is not "right" or "wrong" since this simply depends on your application. at and emplace are prime examples that are highly advisable.
Trying to learn C++ coming from Python, and in python a set can have multiple types. How do I do this in C++? I'm specifically trying to have a set with both integers and strings. For example:
#include <set>
#include <string>
using namespace std;
int main() {
set<int, string> s;
s.insert(1);
s.insert("string");
}
Having multiple types of elements in a container is called a heterogenous container.
C++ supports this from C++17 using std::any which can hold any type, or as EOF said using std::variant when you want to define the set of possible types yourself.
Here is a demo of std::any using std::any_cast:
#include <any>
#include <iostream>
#include <list>
#include <map>
#include <set>
int main()
{
std::list<std::any> any_list;
int myInt = 1;
std::string myString("I'm a string");
using MapType = std::map<std::list<int>, std::string>;
MapType myMap;
struct CustomType {
void* pointer;
};
any_list.emplace_back(std::any());
any_list.emplace_back(myInt);
any_list.emplace_back(myString);
any_list.emplace_back(myMap);
any_list.emplace_back(CustomType());
// To show the awesome power of std::any we add
// the list as an element of itself:
any_list.emplace_back(any_list);
for(auto& element: any_list) {
if(!element.has_value()) {
std::cout << "Element does not hold a value" << std::endl;
continue;
}
if (int* someInt = std::any_cast<int>(&element)) {
std::cout << "Element is int: " << *someInt << '\n';
} else if (std::string* s = std::any_cast<std::string>(&element)) {
std::cout << "Element is a std::string: " << *s << '\n';
} else if (std::any_cast<MapType>(&element)) {
std::cout << "Element is of type MapType\n";
} else if (std::any_cast<CustomType>(&element)) {
std::cout << "Element is of type CustomType\n";
} else {
std::cout << "Element is of unknown but very powerful type\n";
}
}
}
This yields output:
Element does not hold a value
Element is int: 1
Element is a std::string: I'm a string
Element is of type MapType
Element is of type CustomType
Element is of unknown but very powerful type
The pre-C++17 method of doing this is obviously a struct with manual type info and void*.
Note that I used std::list instead of std::set because std::any does not have operator< defined by default. This could be solved by defining your own comparison predicate.
My personal opinion is that usually when you think you want to use a heterogenous container it's worth re-evaluating your design and stick to normal homogenous containers, but it's there if you need it :-)
I want my function to return a string, but only strings which are a member of a specific list/set of strings. How can I go about doing this?
You do not want to return a string, you want to return a string that has an additional restriction (being part of some predefined set).
For that you'd need a new type:
class BusinessStringWrapper {
public:
BusinessStringWrapper(std::string arg): value{arg} {
if (/* arg is not ok */) {
throw;
}
}
// you can replace that with factory method
// can also return std::optional instead of throwing if the condition is not met
// that depends on your application
std::string value() const { return value; }
private:
const std::string value;
};
And in your application you'd operate on this type, accessing value if needed.
Hoe about using a std::set<std::string>?
#include <iostream>
#include <set>
#include <string>
std::string helper(const std::string & str,
const std::set<std::string> & lst)
{
return lst.find(str) == lst.end() ? "" : str;
}
int main()
{
std::set<std::string> lst = {"alpha", "beta", "gamma"};
std::cout << "return " << helper("alpha", lst) << "\n";
std::cout << "return " << helper("zeta", lst) << "\n";
return 0;
}
Output
return alpha
return
Of course, it really depends on what your definition of does not return is.
If it means an empty string, then use the above solution. Keep your life simple.
If it means an error and the program should terminate, you may #include <cassert> and just
assert(lst.find(str) != lst.end());
If it means an exception to handle, you may try throw and catch.
If it means returning a std::string if str is in a predefined list, but a void if it's not, then you may need some tricks as described in <type_traits>.
You can do this std::map<CardType, std::string> in the example below, or use std::map<int, std::string> to associate a string with any integer. For example mp[123]="abcd"
#include <iostream>
#include <string>
#include <map>
enum CardType {
SPADE,
HEART,
CLUBS,
DIAMD
};
std::map<CardType, std::string> mp{
{CardType::SPADE, "Spade"},
{CardType::HEART, "Heart"},
{CardType::CLUBS, "Clubs"},
{CardType::DIAMD, "Diamond"}
};
int main()
{
std::cout << mp[CardType::SPADE] << std::endl;
return 0;
}
I have a large series of functions that all look very similar: they take the same arguement type and return strings.
std::string f1(T arg);
std::string f2(T arg);
std::string f3(T arg);
std::string f4(T arg);
.
.
.
In a loop, they are used according to one of the variables inside the struct T. Currently to do this, I just have a large switch/case block in my code.
Is there any better coding style for doing this? The large block of code looks very weird.
I wish c++ could be like python and do eval("f" + str(i) + "(arg))"
The block is something like this:
std::string out = "";
switch (arg.tag){
case 1:
out += f1(arg);
break;
case 2:
out += f2(arg);
break;
.
.
.
}
for about 2 dozen cases
With C++11 you can do this fairly easily with std::function and a map:
#include <map>
#include <functional>
#include <string>
#include <iostream>
std::string f1(int) { return "f1"; }
std::string f2(int) { return "f2"; }
std::map<int, std::function<std::string(int)> > funcs = {
{1,f1},
{2,f2}
};
int main() {
std::cout << funcs[1](100) << "\n";
}
Without C++11 you'll want to either use Boost instead of std::function or roll your own type instead. You could use plain old function pointers but that would rule out some handy things (like std::bind/boost::bind, functor objects, lambda functions. You could also define a type hierarchy with an interface that your functions implement for example the following works in C++03 except for the way the map is initialised:
#include <map>
#include <functional>
#include <string>
#include <iostream>
std::string f1(int) { return "f1"; }
std::string f2(int) { return "f2"; }
std::map<int, std::string(*)(int)> funcs = {
std::make_pair(1,f1),
std::make_pair(2,f2)
};
int main() {
std::cout << funcs[1](100) << "\n";
}
or this which lets you write any kind of functor object you like:
#include <map>
#include <string>
#include <iostream>
struct thing {
virtual std::string operator()(int) const = 0;
};
struct f1 : thing {
std::string operator()(int) const { return "f1"; }
};
struct f2 : thing {
std::string operator()(int) const { return "f2"; }
};
// Note the leak - these never get deleted:
std::map<int, thing*> funcs = {
std::make_pair(1,new f1),
std::make_pair(2,new f2)
};
int main() {
std::cout << (*funcs[1])(100) << "\n";
}
One way to emulate the Eval() is to have a map. The key of the map would be the names of the functions, and the values would be the pointers to the corresponding functions.
In this case you will be able to call the functions needed with the map's operator[] by their name. This will somehow emulate the eval("f" + str(i) + "(arg))" behavior, though it may still not be the best solution for you.