0

I built a simple JSON encoder/decoder in C++ (I know there are many excellent solutions for this problem out there; I'm learning the language, and it's simply a challenge to myself, and I'm not about to pretend that my solution is any good).

Initially, I had the following two operator [] overloads:

json& operator[](const std::string& key) {
  assert_type_valid(/* "object" type */);
  return (*object_data_)[key];
}

json& operator[](size_t index) {
  assert_type_valid(/* "array" type */);
  return (*array_data_)[index];
}

...

std::unique_ptr<std::unordered_map<std::string, json>> object_data_;
std::unique_ptr<std::vector<json>> array_data_;

And this worked great! I could do something like my_json["someKey"]["someOtherKey"][4]["thirdKey"] to access nested values.

But then, because my project uses a bunch of Windows APIs that make use of both char/std::string and wchar_t/std::wstring, I wanted to make both json and wjson types, so I turned my existing json class and parser into a basic_json<CharType, StringType>.

Now, the same code looks like:

// Using C for CharType and S for StringType for brevity here...

basic_json<C, S>& operator[](const S& key) {
  assert_type_valid(/* "object" type */);
  return (*object_data_)[key];
}

basic_json<C, S>& operator[](size_t index) {
  assert_type_valid(/* "array" type */);
  return (*array_data_)[index];
}

...

std::unique_ptr<std::unordered_map<S, basic_json<C, S>>> object_data_;
std::unique_ptr<std::vector<basic_json<C, S>>> array_data_;

Attempting to use either operator [] now results in this error:

error C2666: 'basic_json<char,std::string>::operator []': 2 overloads have similar conversions

could be ...::operator [](size_t)
or       ...::operator [](const S &)
or       built-in C++ operator[(__int64, const char [8])

I've tried adding a third operator [](const C* key) overload, but that hasn't made a difference.

My actual question: Why has templating caused this?

I'm under the impression that template types are determined at compile time, so presumably the compiler should understand what C and S are and be able to know that I'm not passing in a size_t when I do my_json["someKey"].

Also, I feel like templating has turned the once-pretty code into a huge mess. Is it ever worth simply duplicating a bunch of code in a situation like this? Like having a second parser that just works explicitly with wchar_t and std::wstring?

Dave
  • 327
  • 1
  • 7
  • This question's shown code fails to meet Stackoverflow's requirements for showing a [mre]. Because of that it's unlikely that anyone here can conclusively answer the question; but only guess at the most. You need to [edit] your question to show a minimal example, no more than one or two pages of code (the "minimal" part), that everyone else can cut/paste ***exactly as shown***, compile, run, and reproduce the described issue (the "reproducible" part, this includes any ancillary information, like any input to the program). See [ask] for more information. – Sam Varshavchik Oct 20 '22 at 12:30
  • Why not just use an `auto` return type? – MSalters Oct 20 '22 at 12:32
  • ...I was unaware that `auto` could be used as a class member function return type. I assumed the signatures always had to be explicitly defined, for whatever reason. – Dave Oct 20 '22 at 12:37
  • Why are you wrapping the member variables in `std::unique_ptr`? – molbdnilo Oct 20 '22 at 12:40
  • [Can't reproduce](http://coliru.stacked-crooked.com/a/1add73a3480bf9b1). Do you have any implicit conversions defined? Implicit conversions are Evil. – molbdnilo Oct 20 '22 at 12:42
  • @molbdnilo It was an implicit conversion after all (see accepted answer). I'm not well versed in parsing C++ error messages yet, so I didn't think it was relevant. Thank you! – Dave Oct 20 '22 at 12:54

1 Answers1

1

"Why has templating caused this?" Because class template members are only instantiated when necessary.

The clue appears to be in the operator[] which you did not mention:

built-in C++ operator[(__int64, const char [8]).

That is to say, the compiler considers 5["someKey"]. Wait, what? Yes, in C++ that is valid. It's the same as "someKey"[5], for compatibility with C.

Now how is that even relevant? Your basic_json<C, S> has an implicit conversion to int and std::string has an implicit conversion from const char [8], so the two overloads are equal.

The fix: remove the implicit conversion to int.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Oh my God, it's because I also added an `operator bool()` at some point during the switch from non-template to template, which I didn't mention because I didn't think it was relevant at all. I'm assuming that, because `bool` is convertible to `int`, that makes the whole class implicitly convertible to `int` as well? Would be really, really nice if the error message made that remotely clear...just one more thing you get used to over time, I suppose. – Dave Oct 20 '22 at 12:52
  • @Dave: And that's not `explicit`? See https://stackoverflow.com/questions/39995573/when-can-i-use-explicit-operator-bool-without-a-cast – MSalters Oct 20 '22 at 12:56
  • No, it was not marked `explicit`. – Dave Oct 20 '22 at 13:00