Home > Net >  Using recursive variadic template parameters in function return type
Using recursive variadic template parameters in function return type

Time:01-10

I am trying to write a variadic template function that can try to any_cast its parameters and return a variant with the first successful cast, I have successfully done this using fold expressions but for fun I tried to write it as a recursive template where I ran into the following error. Which is caused by the fact that the return type is changed for every recursive instantiation.

error: no viable conversion from returned value of type 'variant<float, std::basic_string<char>, (no argument)>' to function return type 'variant<int, float, std::basic_string<char>>'

here is my function

template <typename T, typename... Ts>
std::variant<T, Ts...> try_any_cast(std::any const & a)
{
    if constexpr (sizeof...(Ts) > 0) 
    {
        if (auto result = std::any_cast<T>(&a)) 
        {
            return std::any_cast<T>(a);
        }

        return try_any_cast<Ts...>(a);
    } 
    else 
    {
        throw std::bad_any_cast();
    }
}

which is expected to be used like

std::any a = 5;
auto value = try_any_cast<int, float, std::string>(a);

How can I store and use the original template parameter pack for all instantiations so that the only and final return type is std::variant<int, float, std::string>?

CodePudding user response:

You can store the original parameter pack by storing the original parameter pack. OK, sounds weird, but that's because you basically described what the solution should look like without actually getting to a solution. It helps to have a helper template. Oops, there I go repeating myself again. Time to get to some details.

The helper can take one additional parameter which is the return type. Otherwise it looks a lot like your current template.

// Add parameter `R` for the return type
template <typename R, typename T, typename... Ts>
R try_any_cast_return(std::any const & a)
{
    if constexpr (sizeof...(Ts) > 0) 
    {
        if (auto result = std::any_cast<T>(&a)) 
        {
            return std::any_cast<T>(a);
        }

        return try_any_cast_return<R, Ts...>(a);
    } 
    else 
    {
        throw std::bad_any_cast();
    }
}

Once you have that, the template you want people to use is just a wrapper that inserts the desired return type.

template <typename... Ts>
std::variant<Ts...> try_any_cast(std::any const & a)
{
    return try_any_cast_return<std::variant<Ts...>, Ts...>(a);
}

There you go. The original parameter pack is stored within a new parameter provided to the helper template, with no impact on how the original template is intended to be used.

CodePudding user response:

In case anyone is also interested in how I solved this issue initially using fold expressions

template <typename... Ts>
auto try_any_cast(std::any any)
{
    std::variant<Ts...> result;
    if (not ((any.type() == typeid(Ts)
                   ? (result = std::variant<Ts...>(std::any_cast<Ts>(any)), true)
                   : false)
              || ...))
        throw std::bad_any_cast();
    return result;
}
  • Related