Return a std::function with auto return type

Viewed 659

I want to create a functor to convert a std::string to different types.

std::function<auto(const std::string)> create(const std::string &type)
{
  if(type=="int") {
    return [&](const std::string &value){return std::stoi(value);}
  } else if(type=="float") {
    return [&](const std::string &value){return std::stof(value);}
  }  else {
    throw std::runtime_error("");
  }
}

But it seems like we cannot use auto as a return type of std::function here.

Can someone suggest a way to do this please?

2 Answers

The return type must be the same and fixed for one function or one instantiation of function template. You can make the function template as

template <typename R>
std::function<R(const std::string&)> create()
{
  if(std::is_same<R, int>::value) {
    return [](const std::string &value){return std::stoi(value);};
  } else if(std::is_same<R, float>::value) {
    return [](const std::string &value){return std::stof(value);};
  }  else {
    throw std::runtime_error("");
  }
}

then use it like

auto f_int = create<int>();
auto f_float = create<float>();

Since C++17 you can use constexpr if, the unnecessary statement would be discarded at compile-time.

template <typename R>
std::function<R(const std::string&)> create()
{
  if constexpr (std::is_same_v<R, int>) {
    return [](const std::string &value){return std::stoi(value);};
  } else if constexpr (std::is_same_v<R, float>) {
    return [](const std::string &value){return std::stof(value);};
  }  else {
    throw std::runtime_error("");
  }
}

BTW: As the return type the parameter of the std::function should be const std::string&. And the lambda seems no need to capture anything.

BTW2: Depending on how you use the return value, returning the lambda directly instead of wrapping it into std::function might be sufficient too.

template <typename R>
auto create()
{
  if constexpr (std::is_same_v<R, int>) {
    return [](const std::string &value){return std::stoi(value);};
  } else if constexpr (std::is_same_v<R, float>) {
    return [](const std::string &value){return std::stof(value);};
  }  else {
    throw std::runtime_error("");
  }
}

I do not think it is possible to have auto return type deduction in a std::function.

If you need to store a lambda/functor that calls the std::sto* function, I've come up with some alternatives. Simply make create a template itself and replace auto with it. This allows you to remove the const std::string& type parameter.

#include <type_traits> // for std::is_same

template <typename T = int>
constexpr std::function<T(const std::string)> create()
{
    if (std::is_same<T, int>::value)   
    { return [&](const std::string &value) { return std::stoi(value); }; }

    if (std::is_same<T, float>::value) 
    { return [&](const std::string &value) { return std::stof(value); }; }

    throw std::runtime_error("");
}

Or you could create a functor class yourself.

#include <type_traits> // for std::is_same

template <typename T = int>
struct Functor {

    T operator()(const std::string& input) {
        if (std::is_same<T, int>::value)   { return std::stoi(input); };
        if (std::is_same<T, float>::value) { return std::stof(input); };
        throw std::runtime_error("");
    }

};

Usage for the above.

int main() {
    
    auto f_i = create();
    auto f_f = create<float>();
    f_i("42");   // returns an int
    f_f("3.14"); // returns a float
    
    Functor fo_i;
    Functor<float> fo_f;
    fo_i("42");   // returns an int
    fo_f("3.14"); // returns a float
    
}

I would suggest that simply calling the correct numeric conversion function instead of storing a lambda/functor that calls std::sto* would be a better approach.

Related