Home > Back-end >  How we make a C function that can assign different types to an auto variable?
How we make a C function that can assign different types to an auto variable?

Time:11-05

How we make a C function that can assign different types to an auto variable?

The nlohmann json package does this, which is proof that it’s possible:

#include <iostream>
#include "./x64/Debug/single_include/nlohmann/json.hpp"
using namespace std;

using json = nlohmann::json;

int main()
{
    nlohmann::json obj = nlohmann::json::parse("{ \"one\": \"111\", \"two\": 222}");

    string res1 = obj["one"];       // Types defined:
    int res2 = obj["two"];

    auto a1 = obj["one"];           // Auto variables:
    auto a2 = obj["two"];

    cout << "Types defined:  " << res1 << ' ' << res2 << endl;
    cout << "Auto variables: " << a1 << ' ' << a2 << endl;
}

Result:

Types defined:  111 222
Auto variables: "111" 222

The auto variables correctly received types string and int.

I have a class that stores different types that I want to assign to auto variables like nlohmann json does.

I’ve tried using implicit conversion methods. The base class below defines int() and string() conversions, which are overridden in the derived classes for int and string. So PayloadParamBase is converted to int or string to match the derived class. The problem is that I can’t get conversion to the right type when PayloadParamBase is assigned to an auto variable. See the result below.

#include <iostream>
#include <unordered_map>
using namespace std;

class PayloadParamBase;
unordered_map<string, PayloadParamBase*> map;

class PayloadParamBase                          // Base class.
{
public:
    virtual void operator= (const int i) { };
    virtual void operator= (const string s) { };

    virtual operator int() const { return 0; };     // Implicit conversions:
    virtual operator string() const { return ""; };
    
    PayloadParamBase& operator[](const char* key) { string tmp(key); return operator[](tmp); }  // Avoids implicit conversion error.
    PayloadParamBase& operator[](const string& key); 
};

PayloadParamBase& PayloadParamBase::operator[] (const string& key)
{
    PayloadParamBase* ptr = map[key];               // Look up a derived class.
    return *ptr;
}

class PayloadStringParam : public PayloadParamBase      // String derived class.
{
public:
    PayloadStringParam(string st) { mValue = st; }
    virtual operator string() const override { return mValue; }
protected:
    string  mValue;
};

class PayloadIntParam : public PayloadParamBase         // Int derived class.
{
public:
    PayloadIntParam(int i) { mValue = i; }
    virtual operator int() const override { return mValue; }
protected:
    int mValue;
};

int main()
{
    map["one"] = new PayloadStringParam("111");
    map["two"] = new PayloadIntParam(222);

    PayloadParamBase pl;

    string strVal = pl["one"];  // Assignment to fixed type works:
    int intVal = pl["two"];
    cout << "Types defined: " << strVal << ' ' << intVal << endl;

    auto res1 = pl["one"];      // Assignment to autos doesn't use type:
    auto res2 = pl["two"];
    cout << "Auto variables: " << res1 << ' ' << res2 << endl;
}

Result:

Types defined: 111 222
Auto variables: 0 0

The auto variables are zeroes and the assignments failed. How can I get conversion to the right type when assigning to an auto variable? Thanks!

CodePudding user response:

It is a fundamental property of C that the types of all objects are defined and are known at compile. This is fundamental to C , there are no workarounds or exceptions.

auto a1 = obj["one"];           // Auto variables:
auto a2 = obj["two"];

Types defined:  111 222
Auto variables: "111" 222

The auto variables correctly received types string and int.

No they do not. If you dig into this library's header file you will discover that nlohmann::json's [] operator returns an object of a specific type...

cout << "Auto variables: " << a1 << ' ' << a2 << endl;

... and this object implements a << overload that formats the contents of the object depending upon whether, in the underlying JSON file, the object is a string, or a numeric value, or maybe something else.

A very simple, trivial learning experience for you would be to use your debugger to set a breakpoint here and have your debugger show you the types of both a1 and a2 objects. You will discover that they are exactly the same type. They are not a string type, or an integer type. They are a class defined in this JSON library that represents an opaque value specified in the JSON file. This is basically a std::variant.

CodePudding user response:

auto does not give you a dynamic type or anything like it; it gives you a statically inferred type, inferred from the type of the expression used to initialize it. So when you say

auto res1 = pl["one"];

it infers the type for res1 based on the expression. Since PayloadParamBase::operator[] returns a PayloadParamBase &, the type for res1 is inferred to be PayloadParamBase. The will then use PayloadParamBase's copy constructor to "slice" the object returned by operator[], copying (just) the base part into res1.

Then, when you go to print it, it will look for an operator << overload that works for PayloadParamBase and will find that it can used the operator int() method to convert it to an int and print it that way. So that is why you get the 0 0 output (PayloadParamBase::operator int() just returns 0)


The slicing you see here demonstrates one of the dangers of combining auto with functions that returns references. auto will never infer a reference type. If you do want the reference type, you can use auto & or decltype(auto) instead (the former will give an error if the return type is not a reference.) When you do this:

decltype(auto) res1 = pl["one"];
decltype(auto) res2 = pl["two"];

your output on the final line is

Auto variables: 0 222

because (as before) it is using operator int() to print these, but this time you have references to the actual objects of derived type you created earlier, so the PayloadIntParam prints its value.

If you want to get the correct output for both derived types, you can overload operator<<(std::ostream, const PayloadParamBase&) to do the right thing -- probably calling a virtual function PayloadParamBase::printOn(std::ostream &) that you define and overload for both derived classes.

  • Related