Home > OS >  In a variadic function template can the return type be deduced from the template parameter pack elem
In a variadic function template can the return type be deduced from the template parameter pack elem

Time:02-05

I am trying to write a variadic function template. The first argument to the function is an integer index value. The rest of the (variable number of) arguments represent the variable number of arguments. This function must return the argument at the location index. For example, if function is invoked as

`find_item(1, -1, "hello", 'Z', 10.03)`

it must return "hello"

If invoked as

find_item(2, -1, "hello", 'Z', 10.03)

it must return 'Z'

If invoked as

find_item(3, -1, "hello", 'Z', 10.03, 'Q', 100)

it must return 10.03 etc....

I was trying something like the following code :

template <class T>
auto find_item(int inx, T val) { return val ;}

// GENERAL CASE
template <class T, class... Others>
auto find_item(int inx, T a1, Others ... others) {
    if(inx==0) return a1 ;
    else return print(inx-1, others...) ;
}

int main() {
    cout<<find_item(1, -1, "hello", string("abvd"), 10.03)<<endl ; ;
}

This code does not compile because the return type not being uniform, can not be deduced. I want to know if there is any way that this can be achieved. Or is it an invalid use case all together. If this can be achieved, then how.

CodePudding user response:

This is not C idiomatic way to deal with it, but you can model returning different types with std::variant. variant_cast is a helper that converts variant<T...> to variant<S...> where S is a superset of T.

#include <variant>
#include <iostream>

template <class... Args>
struct variant_cast_proxy
{
    std::variant<Args...> v;

    template <class... ToArgs>
    operator std::variant<ToArgs...>() const
    {
        return std::visit([](auto&& arg) -> std::variant<ToArgs...> { return arg ; },
                          v);
    }
};

template <class... Args>
auto variant_cast(const std::variant<Args...>& v) -> variant_cast_proxy<Args...>
{
    return {v};
}


template <class T>
std::variant<T> find_item(int inx, T val) { return val ;}

// GENERAL CASE
template <class T, class... Others>
std::variant<T, Others...> find_item(int inx, T a1, Others ... others) {
    if(inx==0) return a1 ;
    else return variant_cast(find_item<Others...>(inx-1, others...)) ;
}



int main() {
    std::visit([](auto v){std::cout << v << std::endl;}, 
               find_item(1, -1, "hello", std::string("abvd"), 10.03)) ;
}

CodePudding user response:

Since your return type is determined by the run time variable inx, there is no way to know the exact return type of find_item at compile time.

What you need to do is type erasure. You can return std::variant<Args...> and construct it with the corresponding arguments based on the value of inx. For example:

#include <variant>
#include <array>
#include <tuple>

template<class... Args>
constexpr auto find_item(std::size_t index, Args... args) {
  constexpr auto indices = []<std::size_t... Is>(std::index_sequence<Is...>) {
    using var_t = std::variant<std::integral_constant<std::size_t, Is>...>;
    return std::array{var_t{std::in_place_index<Is>}...};
  }(std::index_sequence_for<Args...>{});

  return std::visit([&...args = args](auto i) {
    return std::variant<Args...>{
      std::in_place_index<i>,
      std::get<i>(std::tuple(args...))};
  }, indices[index]);
}

Demo

CodePudding user response:

I want to know if there is any way that this can be achieved.

No.

In C/C (that are statically typed languages) the type returned from a function must depends from the types of the arguments, not from the values.

So the types returned from the following calls

find_item(1, -1, "hello", 'Z', 10.03);
find_item(2, -1, "hello", 'Z', 10.03);
find_item(3, -1, "hello", 'Z', 10.03);

must be the same.

Point.

You can get around this rule returning a std::variant (that can contain values of different types), or a std::any (that can contain generic values), but a type that is determined independently from the received value.

Different if you pass the index of type/value you want (the first argument, in your case) as a template parameter.

I mean: different if you call find_item() as follows

find_item<1>(-1, "hello", 'Z', 10.03);
find_item<2>(-1, "hello", 'Z', 10.03);
find_item<3>(-1, "hello", 'Z', 10.03);

Now find_item<1>() can return a char const *, find_item<2>() can return a char and find_item<3>() can return a double.

This is because find_item<1>(), find_item<2>() and find_item<3>() are different functions, so can have different return types.

But... this way... we've almost obtained the std::get<>() that extract values from a std::tuple.

Unfortunately you can use this solution only when the index (the template parameter) is known compile time.

In other words, you can't make something as follows

for ( auto i = 1 ; i < 4 ;   i )
 { // .............NO! ---V
   auto value = find_item<i>(-1, "hello", 'Z', 10.03);
 }
   
  •  Tags:  
  • Related