Question

I've been trying to make a very simple map container and I thought it would be nice to be able to initialize it like so:

Hash table = {  "name1" >> value,
                "name2" >> value2,
                "name3" >> value3,
                "name4" >> value4  };

How I was going to go about this was by first making a pair(duo) data structure which would keep the name and the value of each element, overload the >> operator to return a duo using the name and value parameters and make a constructor for the hash class to initialize it by using the initializer_list to construct a vector of the duo elements.Then use binary search methods to retrieve the wanted element.

Unfortunately I hit a wall from the very start. Before I started coding everything I wanted to make sure that the overloads were used correctly and in fact it was a smart decision since apparently they are not.

This works:

#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>

struct duo {
  duo(const std::string key,int value) :key(key) ,value(value) {};
  const std::string key;
  int value;
};

struct Hash {
  std::vector<duo> lister;
  Hash(std::initializer_list<duo> passed) :lister(passed) {};
};

duo operator >> (const std::string& id,int value) {
  return duo(id,value);
}

int main(){
  duo object1("test",1);
  Hash table = {object1};
  std::cout << table.lister[0].key << table.lister[0].value;
}

but this gives "invalid operands of types ‘const char [6]’ and ‘int’ to binary ‘operator>>’"

#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>

struct duo {
  duo(const std::string key,int value) :key(key) ,value(value) {};
  const std::string key;
  int value;
};

struct Hash {
  std::vector<duo> lister;
  Hash(std::initializer_list<duo> passed) :lister(passed) {};
    };

duo operator >> (const std::string id,int value) {
  return duo(id,value);
}

int main(){
  Hash table = {"test1" >> 1};
  std::cout << table.lister[0].key << table.lister[0].value;
}

I tried to pass std::string to the >> operator because I can't overload with primitive types. It seems this is not a solution though. Is there any way to achieve the desired effect without explicitly converting the string literals to std::string ?

Was it helpful?

Solution

Constructing a string explicitly will resolve this:

int main(){
  Hash table = {std::string("test1") >> 1};
  std::cout << table.lister[0].key << table.lister[0].value;
}

I would also have >> take a reference to a const string, not a string by value:

duo operator >> (const std::string& id,int value) {
  return duo(id,value);
}

You might think that you could avoid the explicit construction by devising something like:

template <size_t N> duo operator >> (const char (&ary)[N], int id)
{
  return duo (std::string (ary), id);
}

...or:

duo operator >> (const char* id, int value)
{
  return duo (std::string (id), value);
}

But you can't because C++ doesn't allow overriding operator >> for pointer (or any primitive) types. If you try this, you will get a compiler error along the lines of "must have an argument of class or enum type." In order to provide an operator overload, you must provide at least one argument which is of class- or enum-type.


So let's give this a bit of a think. We can't use operator<< with two primitive arguments because that's not allowed. What you really are looking for is clean initialization syntax without any explicit initialization. The only thing that's really standaing in our way is that initialization. So what if we try to construct something that actually employs two operators, but still looks clean? What if we try to construct something like:

Hash table = {"test1" <MapTo> 1};

It's not exactly the syntax you wanted, but it's fairly close. So let's try to build this:

There are two operators at work here: operator< and operator>. The arguments to operator< are the string literal "test1 and some object named MapTo. The arguments to operator> are whatever operator< returns and an integral literal, 1.

Looking at operator< first, let's hack up a prototype:

template <size_t N> std::string operator < (const char (&ary)[N], Something op)

We know that we're going to want operator> to return a duo, so what does Something need to be in order to facilitate that? All we really need operator< to do is convert the string literal to a std::string, so let's try:

class HashOperation
{
public:
  template <size_t N> std::string cvt (const char (&ary)[N]) const
  {
    return std::string (ary);
  }
} MapTo;


template <size_t N> std::string operator < (const char (&ary)[N], HashOperation op) 
{
  return op.cvt (ary);
}

Now we've got the std::string, let's build a duo out of that along with the int.

duo operator> (const std::string& key, int value)
{
  return duo (key, value);
}

That was pretty easy. Let's put it all together (Live Demo):

#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>

struct duo {
  duo(const std::string key,int value) :key(key) ,value(value) {};
  template <size_t N> duo (const char (&ary)[N], int value) : key (ary), value (value) {};
  const std::string key;
  int value;
};

struct Hash {
  std::vector<duo> lister;
  Hash(std::initializer_list<duo> passed) :lister(passed) {};
    };


class HashOperation
{
public:
  template <size_t N> std::string cvt (const char (&ary)[N]) const
  {
    return std::string (ary);
  }
} MapTo;


template <size_t N> std::string operator < (const char (&ary)[N], HashOperation op)
{
  return op.cvt (ary);
}

duo operator> (const std::string& key, int value)
{
  return duo (key, value);
}

int main(){
  Hash table =
  {
    "test1" <MapTo> 1,
    "test2" <MapTo> 2
  };

  std::cout << table.lister[0].key << table.lister[0].value;
}

OTHER TIPS

You're trying to overload operator>> for two primitive types, char const * and int; this is not allowed by the language. To get it to work in its current form, you'll need to explicitly create an std::string from the first argument

Hash table = {std::string("test1") >> 1};

Another option, of course, is to forgo this syntax for initialization, and stick to comma separated pairs.

#include <iostream>
#include <string>
#include <vector>
#include <initializer_list>
#include <utility>

using duo = std::pair<std::string, int>;

struct Hash {
  std::vector<duo> lister;
  Hash(std::initializer_list<duo> passed) :lister(passed) {};
};

int main(){
  Hash table = {{"test1", 1}, {"test2", 2}};
  std::cout << table.lister[0].first << table.lister[0].second;
}

You defined operator>> for std::string and int, yet you try to use it with arguments const char[] and int. You need to let the compiler know that the first argument is std::string. Try {"test1"s >> 1}

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top