Creating a class indexer operator[] allowing string parameter (string index) - c++

I want to create a class in c++. This class must manage with a collection.
OK, no problem, I would like to use operator[] of course but, in this case, my wish is to index not by position, but by name ==> that means using a string indexer.
It seems that something of this kind is not so nice to my compiler:
// In hpp
class myclass {
...
...
std::string operator[](const std::string& name);
}
// In cpp
std::string myclass::operator[](const std::string& name) {
...
}
// In main
myclass m;
std::string value = m["Name"];
Compiler tells me that he cannot solve this because operator[const char[5]] does not exists.
OK OK
I could figure this...
Compiler thinks that by calling m["Name"] I'm trying to call an operator admitting a char* and not a string... ok
Let's change the code with operator[] allowing a char* as parameter... nothing.
Can somebody tell me how to achieve such a result in c++ in a best practice way? I suppose that is a common problem to index by string and not by integer...
Thank you.

The code that you have provided should compile okay (providing the operator is public and you terminate your class declaration with a ;). I suspect the compiler error is somewhere else.
Personally, I would use std::map<std::string, std::string> as the container class.
#include <string>
#include <map>
#include <assert.h>
int main()
{
std::map<std::string, std::string> m;
m["Foo"] = "Bar";
m["Fez"] = "Baz";
assert(m["Foo"] == "Bar");
assert(m["Fez"] == "Baz");
}

It should work fine. See this example which compiles and works ok for me:
#include <iostream>
#include <string>
class MyClass
{
public:
std::string operator[] (const std::string& key) { std::cout << key << std::endl; return key; }
};
int main()
{
MyClass obj;
std::string s = obj["50"];
std::cout << s << std::endl;
}
And I see no reason it should not, since std::string has implicit constructor taking const char* so the conversion should be automatic.
Edit: From the comment it seems your problem was with your main beeing like this:
int main()
{
MyClass obj();
std::string s = obj["50"];
std::cout << s << std::endl;
}
The reason:
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
[ Note: since () is not permitted by the syntax for initializer,
X a ();
is not the declaration of an object of class X, but the declaration of a function taking no argument and returning an X.
The form () is permitted in certain other initialization contexts (5.3.4, 5.2.3, 12.6.2). — end note ]

Related

Why does C++ make the element type of std::initializer_list const?

I commonly use std::initializer_list<std::string>, say {"foo"s, "bar"s}, to fill a container.
Obviously, std::initializer_list<std::string> contains temporary strings, and I can move them into a container:
#include <string>
#include <list>
using namespace std::literals;
void f(std::initializer_list<std::string> il)
{
std::list<std::string> coll;
for (auto&& tmp_str : il)
{
coll.emplace_back(std::move(tmp_str)); // Why ok here?
}
}
int main()
{
f({"foo"s, "bar"s});
}
However, according to cppref:
An object of type std::initializer_list is a lightweight proxy
object that provides access to an array of objects of type const T.
Why does C++ make the element type of std::initializer_list const?
Obviously, std::initializer_list<std::string> contains temporary strings
Does it, though? It is allowed to be implemented it as a thin proxy. In which case it would refer to the original instances "foo"s, "bar"s.
... and I can move them into a container
I doubt that. std::move() of a const lvalue-reference produces... a const rvalue-reference. That won't quite match a move-constructor as a viable candidate.
#include <iostream>
struct A {
A() { std::cout << "A()\n"; }
A(A&&) { std::cout << "A(&&)\n"; }
A(A const&) { std::cout << "A(const&)\n"; }
};
int main() {
const A a;
A b(std::move(a));
}
Prints
A()
A(const&)
MSVC 2019 even warns:
Warning C26478 Don't use std::move on constant variables. (es.56).
See also Why can we use std::move on a const object?

std::string can be passed as nlohmann::json to explicit constructor

Why does the following code compile, even though I am passing a std::string object to a explicit constructor which expects a nlohmann::json (to the library) object? My understanding is that std::string won't be implicit converted due to the explicit keyword.
Is it possible to change my code so that it will compile successful only when a nlohmann::json is passed?
I use Visual Studio 2019 in Debug Mode with /Wall.
#include <nlohmann/json.hpp>
struct A {
explicit A(nlohmann::json json) {
}
};
int main() {
std::string a = "hello";
A b(a);
}
Why does the following code compile, even though I am passing a std::string object to a explicit constructor which expects a nlohmann::json (to the library) object? My understanding is that std::string won't be implicit converted due to the explicit keyword.
The explicit in the A constructor just means that the A constructor must be called explicitly (which you are). The compiler is allowed to use implicit conversions when passing arguments to the A constructor, unless they use types that are also explicit themselves (which the nlohmann::json constructor is not).
Is it possible to change my code so that it will compile successful only when a nlohmann::json is passed?
You can pass the argument by a non-const reference, preventing the compiler from passing an implicitly created temporary object:
struct A {
explicit A(nlohmann::json &json) {
}
};
This might be overkill but the following appears to work while also allowing a const-reference as constructor argument:
#include <nlohmann/json.hpp>
#include <type_traits>
struct A {
template<typename T, typename = std::enable_if_t<std::is_same_v<T, nlohmann::json>>>
explicit A(const T& json) {
}
};
int main() {
const std::string a = "hello";
// A b(a); // ERROR
const auto json_val = nlohmann::json::parse("{}") ;
A c {json_val} ;
}

Instance of Most Vexing Parse with std::string and char*

This is a follow-up to my previous question: C++ compile error constructing object with rvalue std::string from which I learned about the Most Vexing Parse.
I understand now the gist of the problem, however there's one leftover item of syntax I still don't quite understand, which I'd like to ask as a standalone question, since the discussions on the previous post were getting quite long.
Given this code:
#include <iostream>
#include <string>
class Foo
{
public:
Foo(double d)
: mD(d)
{
}
Foo(const std::string& str)
{
try
{
mD = std::stod(str);
}
catch (...)
{
throw;
}
}
Foo(const Foo& other)
: mD(other.mD)
{
}
virtual ~Foo() {}
protected:
double mD;
};
class Bar
{
public:
Bar(const Foo& a, const Foo& b)
: mA(a)
, mB(b)
{
}
virtual ~Bar() {}
protected:
Foo mA;
Foo mB;
};
int main(int argc, char* argv[])
{
if (argc < 3) { return 0; }
Foo a(std::string(argv[1]));
Foo b(std::string(argv[2]));
Bar wtf(a, b);
}
I understand, now, that the line Foo a(std::string(argv[1])); can be interpreted as either:
(1) Create a Foo named a with an anonymous std::string that is created with a char*. (My desired interpretation)
or
(2) A declaration (not definition) for a function named a that takes a std::string*.
From answers to the original question, I learned that functions could be declared within the scope of another function. That was new to me, but seems within reason, I can buy it.
What I can't wrap my head around, though, is the interpretation of std::string(argv[1]) as a std::string*.
argv[1] is a char*, so I still don't see why the line isn't interpreted as an anonymous std::string being constructed with a char*. After all, I've used code analogous to the following hundreds of times without ever scrutinizing whether this would result in anything other than the construction of a std::string with its char* constructor:
#include <iostream>
int main()
{
char* pFoo[] = {"foo"};
std::string str(pFoo[0]);
std::cout << str << std::endl;
return 0;
}
I'm on the cusp of understanding the most vexing parse problem; if someone could further explain this last niggling part, that might help push me over the edge.
Thank you.
Foo a(std::string(argv[1]));
declares a function named a which returns Foo and has one parameter (named argv) of type std::string[1]. Since array function parameters are always replaced with pointer parameters, the actual type of the function parameter becomes std::string*.

Operator overloading c++ (<<)

This the below program i have written for some test.
class tgsetmap
{
public:
std::map<std::string,std::string> tgsetlist;
void operator<<(const char *str1,const char *str2)
{
tgsetlist.insert( std::map<std::string,std::string>::value_type(str1,str2));
}
};
int main()
{
tgsetmap obj;
obj<<("tgset10","mystring");
obj.tgsetlist.size();
}
This throws a compilation error:
"test.cc", line 10: Error: Illegal number of arguments for tgsetmap::operator<<(const char, const char*).
"test.cc", line 22: Error: The operation "tgsetmap << const char*" is illegal.
2 Error(s) detected.*
Am i wrong some where?
You can't force operator<< to take two arguments on right-hand side. The following code:
obj<<("tgset10","mystring");
does not work as a function call with two arguments but instead just uses the , operator. But it's probably not what you are interested in.
If you need to pass two arguments to the << operator, you need to wrap them in some other (single) type. For example, you could use the standard std::pair, i.e. std::pair<const char*, const char*>.
But note that the operator<< should also return some reasonable type suitable for << chaining. That would probably be a tgsetmap& in your case. The following version should work fine:
#include <map>
#include <string>
#include <iostream>
class tgsetmap
{
public:
typedef std::map<std::string, std::string> list_type;
typedef list_type::value_type item_type;
list_type tgsetlist;
tgsetmap& operator<<(item_type item)
{
tgsetlist.insert(item);
return *this;
}
};
int main()
{
tgsetmap obj;
obj << tgsetmap::item_type("tgset10","mystring")
<< tgsetmap::item_type("tgset20","anotherstring");
std::cout << obj.tgsetlist.size() << std::endl;
}
Note that I've added typedefs to not have to repeat the type names over and over again. I've also made operator<< return a tgsetmap& so that << could be chained (used like in the modified main() above). And finally, I've reused the std::map<...>::value_type to make it simpler but you could also use any other type of your own.
But I believe that you may prefer using a regular method instead. Something like:
void add(const char *str1, const char *str2)
{
tgsetlist.insert( std::map<std::string, std::string>::value_type(str1, str2));
}
(inside the class declaration), and then:
obj.add("tgset10", "mystring");
The operator<< inside of a class must be overloaded like this:
T T::operator <<(const T& b) const;
If you want to overload it with 2 arguments, you can do it outside of a class:
T operator <<(const T& a, const T& b);
My compiler, for example, gives a more detailed error message for the code you posted:
If you are not sure about an operator overloading syntax, there is a wiki article about it.
Yes. operator << is binary operator. not ternary. not forget about this pointer.
As mentioned, the << is binary operator, so there is no way it can take more than two args(One should be this if you are declaring inside the class or a LHS if you are declaring outside the class). However you can accomplish the same functionality by doing obj<<"tgset10". <<"mystring";. But since << is a binary operator, you have to do some hack for this.
For this, I ve assigned a static variable op_count, where in I will determine if it is the value or the type. And another static variable temp_str to store the previous value across invocations.
class tgsetmap
{
public:
std::map<std::string,std::string> tgsetlist;
static int op_count = 0;
static const char *temp_str;
tgsetmap& operator<<(const char *str)
{
op_count++;
if (op_count%2 != 0) {
temp_str = str;
}
else {
tgsetlist.insert( std::map<std::string,std::string>::value_type(temp_str,str));
}
return this;
}
};
So you can do
int main()
{
tgsetmap obj;
obj<<"tgset10"<<"mystring";
obj.tgsetlist.size();
}
Or simply you can embed the value and type in the same string using some separator,
value:type = separator is :
value_type = separator is _.

How may I forbid calls to const member function of an rvalue object in C++ 2011?

The following code
#include <vector>
#include <string>
#include <iostream>
std::string const& at(std::vector<std::string> const& n, int i)
{
return n[i];
}
std::vector<std::string> mkvec()
{
std::vector<std::string> n;
n.push_back("kagami");
n.push_back("misao");
return n;
}
int main()
{
std::string const& s = at(mkvec(), 0);
std::cout << s << std::endl; // D'oh!
return 0;
}
may lead to crash because the original vector is already destructed there. In C++ 2011 (c++0x) after rvalue-reference is introduced in, a deleted function declaration can be used to completely forbid calls to at if the vector argument is an rvalue
std::string const& at(std::vector<std::string>&&, int) = delete;
That looks good, but the following code still cause crash
int main()
{
std::string const& s = mkvec()[0];
std::cout << s << std::endl; // D'oh!
return 0;
}
because calls to member function operator [] (size_type) const of an rvalue object is still allowed. Is there any way can I forbid this kind of calls?
FIX:
The examples above is not what I did in real projects. I just wonder if C++ 2011 support any member function qualifying like
class A {
void func() rvalue; // Then a call on an rvalue object goes to this overload
void func() const;
};
FIX:
It's great, but I think C++ standard goes too far at this feature. Anyway, I have following code compiled on clang++ 2.9
#include <cstdio>
struct A {
A() {}
void func() &
{
puts("a");
}
void func() &&
{
puts("b");
}
void func() const &
{
puts("c");
}
};
int main()
{
A().func();
A a;
a.func();
A const b;
b.func();
return 0;
}
Thanks a lot!
No, and you shouldn't. How am I to do std::cout << at(mkvec(), 0) << std::endl;, a perfectly reasonable thing, if you've banned me from using at() on temporaries?
Storing references to temporaries is just a problem C++ programmers have to deal with, unfortunately.
To answer your new question, yes, you can do this:
class A {
void func() &; // lvalues go to this one
void func() &&; // rvalues go to this one
};
A a;
a.func(); // first overload
A().func(); // second overload
Just an idea:
To disable copying constructor on the vector somehow.
vector ( const vector<T,Allocator>& x );
Implicit copying of arrays is not that good thing anyway. (wondering why STL authors decided to define such ctor at all)
It will fix problems like you've mentioned and as a bonus will force you to use more effective version of your function:
void mkvec(std::vector<std::string>& n)
{
n.push_back("kagami");
n.push_back("misao");
}