senseiwa
senseiwa

Reputation: 2499

Passing function with defaults as argument ignoring them

I need to convert a string that represents a vector of Ts into the corresponding vector.

My problem is that I'd like to pass a simple argument: the converter function. Here's the split:

template <class T>
auto split(const std::string &s, const std::function<T(const std::string&)> &convert, char sep = ',') -> std::vector<T>
{
    std::stringstream ss(s);
    std::vector<T> result;

    while (ss.good())
    {
        std::string substr;
        std::getline(ss, substr, sep);
        if (!substr.empty())
            result.push_back(convert(substr));
    }
    return result;
};

It fails to compile when passing standard functions such as std::stoi because of the default parameters of std::stoi as its signature is int stoi(const string& __str, size_t* __idx = 0, int __base = 10);:

auto q = split<int>(subs, std::stoi);

error: no matching function for call to 'split'
            auto q = split<int>(subs, std::stoi);
                     ^~~~~~~~~~

And obviously I can trick the compiler by using a lambda function:

auto q = split<std::size_t>(subs, [](const std::string &s){ return std::stoul(s); });

Is there a metaprogramming trick that allows me to somehow ignore the default parameters?

Upvotes: 3

Views: 303

Answers (2)

Daniel H
Daniel H

Reputation: 7453

EDIT: This doesn’t actually help in this case. I’m leaving it because it’s useful in some other cases, such as if you had a function which returned something convertible to T, but it doesn’t address any of the issues with stoi.


Don’t explicitly specify the type of the function. Let convert be any type; you’ll get an error if you try passing something which cannot be called on a std::string or which doesn’t return something convertible to a T. There’s no reason to constrain the type beyond that unless you specifically have a reason it needs to be that specific type, and in this case you don’t.

Thus, you can declare your function as

template <class T, class F>
auto split(const std::string &s, const F&& convert, char sep = ',') -> std::vector<T>

Upvotes: 5

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype( __VA_ARGS__ ) \
  { return __VA_ARGS__; }

#define OVERLOADS_OF(...) \
  [](auto&&...args) \
  RETURNS( __VA_ARGS__( decltype(args)(args)... )

This macro lets you take the name of a function and generate a lambda that contains the overloads of it.

auto q = split<std::size_t>( subs, OVERLOADS_OF(std::stroul) );

which is nice and concise.

Default arguments are only accessible by invoking () on the actual name of the function, and the only way to "move the name" into a different context is to stuff it into a lambda as text.

As an aside, there is a proposal by @barry to replace RETURNS(X) above with => X for lambdas. I am unaware of a currently maintained proposal to replace the OVERLOADS_OF macro (there was one a while back).

Possibly the reflection proposal would permit you to gain access to the default arguments and overload set of a function name, and fancy metaprogramming would then let you generate OVERLOADS_OF without a macro.

Upvotes: 4

Related Questions