C++ same function parameters with different return

2019-01-25 10:44发布

问题:

I need to find some way to mock an overload of a function return type in C++.

I know that there isn't a way to do that directly, but I'm hoping there's some out-of-the-box way around it. We're creating an API for users to work under, and they'll be passing in a data string that retrieves a value based on the string information. Those values are different types. In essence, we would like to let them do:

int = RetrieveValue(dataString1);
double = RetrieveValue(dataString2);
// Obviously, since they don't know the type, they wouldn't use int =.... It would be:
AnotherFunction(RetrieveValue(dataString1)); // param of type int
AnotherFunction(RetrieveValue(dataString2)); // param of type double

But that doesn't work in C++ (obviously). Right now, we're having it set up so that they call:

int = RetrieveValueInt(dataString1);
double = RetrieveValueDouble(dataString2);

However, we don't want them to need to know what the type of their data string is.

Unfortunately, we're not allowed to use external libraries, so no using Boost.

Are there any ways we can get around this?

Just to clarify, I understand that C++ can't natively do it. But there must be some way to get around it. For example, I thought about doing RetrieveValue(dataString1, GetType(dataString1)). That doesn't really fix anything, because GetType also can only have one return type. But I need something like that.

I understand that this question has been asked before, but in a different sense. I can't use any of the obvious answers. I need something completely out-of-the-box for it to be useful to me, which was not the case with any of the answers in the other question asked.

回答1:

You've to start with this:

template<typename T>
T RetrieveValue(std::string key)
{
     //get value and convert into T and return it
}

To support this function, you've to work a bit more, in order to convert the value into the type T. One easy way to convert value could be this:

template<typename T>
T RetrieveValue(std::string key)
{
     //get value
      std::string value = get_value(key, etc);

      std::stringstream ss(value);
      T convertedValue;
      if ( ss >> convertedValue ) return convertedValue;
      else throw std::runtime_error("conversion failed");
}

Note that you still have to call this function as:

int x = RetrieveValue<int>(key);

You could avoid mentioning int twice, if you could do this instead:

Value RetrieveValue(std::string key)
{
     //get value
      std::string value = get_value(key, etc);
      return { value };
}

where Value is implemented as:

struct Value
{
    std::string _value;

    template<typename T>
    operator T() const   //implicitly convert into T
    {
       std::stringstream ss(_value);
       T convertedValue;
       if ( ss >> convertedValue ) return convertedValue;
       else throw std::runtime_error("conversion failed");
    }
}

Then you could write this:

int    x = RetrieveValue(key1);
double y = RetrieveValue(key2);

which is which you want, right?



回答2:

If you know your value can never be something like zero or negative, just return a struct holding int and double and zero out the one you don't need...

It's a cheap and dirty, but easy way...

struct MyStruct{
int myInt;
double myDouble;
};

MyStruct MyFunction(){
}


回答3:

Whether it is an overload or a specialization, you'll need the information to be in the function signature. You could pass the variable in as an unused 2nd argument:

int RetrieveValue(const std::string& s, const int&) {
  return atoi(s.c_str());
}
double RetrieveValue(const std::string& s, const double&) {
  return atof(s.c_str());
}

int i = RetrieveValue(dataString1, i);
double d = RetrieveValue(dataString2, d);


回答4:

The only sane way to do this is to move the return value to the parameters.

 void retrieve_value(std::string s, double& p);
 void retrieve_value(std::string s, int& p);
 <...>

 double x;
 retrieve_value(data_string1, x);

 int y;
 retrieve_value(data_string2, y);


回答5:

If the datastrings are compile-time constants (as said in answering my comment), you could use some template magic to do the job. An even simpler option is to not use strings at all but some data types which allow you then to overload on argument.

struct retrieve_int {} as_int;
struct retrieve_double {} as_double;

int RetrieveValue(retrieve_int) { return 3; }
double RetrieveValue(retrieve_double) { return 7.0; }

auto x = RetrieveValue(as_int);    // x is int
auto y = RetrieveValue(as_double); // y is double


回答6:

Unfortunately there is no way to overload the function return type see this answer Overloading by return type



回答7:

int a=itoa(retrieveValue(dataString));
double a=ftoa(retrieveValue(dataString));

both return a string.



回答8:

As an alternative to the template solution, you can have the function return a reference or a pointer to a class, then create subclasses of that class to contain the different data types that you'd like to return. RetrieveValue would then return a reference to the appropriate subclass.

That would then let the user pass the returned object to other functions without knowing which subclass it belonged to.

The problem in this case would then become one of memory management -- choosing which function allocates the returned object and which function deletes it, and when, in such a way that we avoid memory leaks.



回答9:

Since you used an example that wasn't really what you wanted, you threw everyone off a bit.

The setup you really have (calling a function with the return value of this function whose return type is unknowable) will not work because function calls are resolved at compile time.

You are then restricted to a runtime solution. I recommend the visitor pattern, and you'll have to change your design substantially to allow for this change. There isn't really another way to do it that I can see.