#Assigning to lvalue reference in tagged union class doesn't change stored value

1 messages · Page 1 of 1 (latest)

tepid needle
#

Hi! I'm writing a small JSON parser project at the moment and have hit a slight wall and am unsure how to make it work on my own.

To represent a value within the JSON data, I have a class Value that is a tagged union that looks like

class Value {
private:
  ValueType m_type;
  union ValueContainer {
    int _int;
    unsigned int _uint;
    bool _bool;
    double _double;
    String _string;
    Object _object;
    Array _array;
  } m_value;
public:
  ...
}

where ValueType is an enum of all the types of values supported by JSON, and Object and Array are just type aliases to std::map<String, Value> and std::vector<Value>

Whenever I want to access the stored value, I use a member function on the value like such

json_data["key-to-access"].as<int>()

This has a declaration that looks like

template<typename T> T as() const

and for each of the types supported by the tagged union, I have a definition that looks like

template<> inline int Value::as<int>() const {
  if (this->m_type != VALUE_INT)
    throw std::runtime_error("Cannot cast as int when value type differs.");
  return this->m_value._int;
}

To edit the JSON from the Object that it is stored in after parsing, I also declared a function within the Value class that is almost identical to the first mentioned:

template<typename T> T& as()

Each definition for all the types is identical to their counterpart in the other function declaration.

Everything compiles fine, but when I try and do things like

json_data["key-to-access"].as<int>() = 42;
// or
auto& num_to_change = json_data["key-to-access"].as<int>();
num_to_change = 42;

The value is left unchanged. This is not how I understand lvalue references to work, and I was anticipating this to be an easy addition to what I have so far.

Where may I be going wrong here? If the code provided isn't enough, I can link what I have written so far to github and share it that way.
Thanks to anyone willing to help out.

tepid needle
#

Thanks for your reply. To clarify, should the function header in this solution return an int or an int&?

#

As fate would have it I was just reading and learning about std::variant this morning, and I agree with you. There's a rewrite in my near future

tepid needle
#

I tried this solution, and the compiler tells me that non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int *'
I am surprised that the expression this->m_value._int is being "interpreted" as a temporary value, but this would make sense as I get no errors when I try to use json_data["key-to-access"].as<int>() = 42; but yet the value is left unchanged.

Why would this be a temporary value? I thought that the return type of the function would force it to be an l-value reference to the corresponding field in the union. Is there a function in the standard library I need to use here, similar to how std::move converts its argument into an r-value reference?

tepid needle
#

Managed to get it working. I should mention that your solution is not what I wanted. I wanted to return an l-value reference, not a pointer. The return type on this, in the case of int, would be int&. Returning &this->m_value._int would require a return type of int *.

The code I provided earlier ended up working as is, without any changes. (I'm not really sure why, maybe I didn't do a make clean? I'm still not sure.)

#

Sure, it's just a simple type alias:

using Object = std::map<String, Value, StringComparator>;
using Array = std::vector<Value>;

where String is my own home-rolled string class.

#

Right, but if I were to try and return &this->m_value._int in a function with return type int& I get a compiler error.

#

This is a map of Strings to Values

#

non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int *'

#

Like I said yesterday