C++ perfect forward on std::tuple

Viewed 92

I drafted the code to find out the index of a value with type V in a tuple elements. Generally, the code works, but the perfect forward for the Tuple seems not to work. Please reference to the last two commented lines in the main function.

#include <type_traits>
#include <iostream>
#include <tuple>
#include <cxxabi.h>
template<int ...INDEX>
struct Sequence
{
};
template<int N, int ...MORE>
struct MkSequence
{
  using type = typename MkSequence<N-1, N-1, MORE...>::type;
};

template<int ... LEFT>
struct MkSequence<0, LEFT...>
{
  using type = Sequence<LEFT...>;
};

template<typename Tuple, int I, typename V>
bool equal(Tuple &&t, V&& v, typename std::enable_if<std::is_same<typename std::tuple_element<I,Tuple>::type, V>::value >::type* = nullptr)
{
  return std::get<I>(std::forward<Tuple>(t)) == std::forward<V>(v);
};

template<typename Tuple, int I, typename V>
bool equal(Tuple &&t, V&& v,  typename std::enable_if<!std::is_same<typename std::tuple_element<I,Tuple>::type, V>::value>::type* = nullptr)
{
  return false;
}

template<typename Tuple, typename V, int ...I>
int tupleIndexOfWithSeq(Tuple &&t, V&& v, Sequence<I...>)
{
  bool list[] = {(equal<Tuple,I,V>(std::forward<Tuple>(t),std::forward<V>(v)))...};
  int i = 0;
  for(auto v: list)
  {
    if(v) return i;
    i++;
  }
  return -1;
};

template<typename Tuple, typename V>
int tupleIndexOf(Tuple &&t, V&& v)
{
  return tupleIndexOfWithSeq(std::forward<Tuple>(t), std::forward<V>(v), typename MkSequence<std::tuple_size<Tuple>::value>::type{});
};
int main()
{
  using TP = std::tuple<int, std::string>;
  TP t(1, "hello");
  std::string str="hello";
  std::cout<<tupleIndexOf(TP(1, "hello"), str)<<std::endl;
  //Why the following calling of tupleIndexOf does not compile:
  //std::cout<<tupleIndexOf(t, str)<<std::endl;
};
1 Answers

This is how forwarding reference works, when being passed lvalues like tupleIndexOf(t, str), the template parameter Tuple would be deduced as an lvalue-reference as std::tuple<int, std::string> &, which makes std::tuple_size<Tuple>::value stops working since std::tuple_size expects an std::tuple type but not a reference to std::tuple. (When being passed rvalue like tupleIndexOf(TP(1, "hello"), str), Tuple will be deduced as std::tuple<int, std::string> exactly then it works well.)

You can use std::remove_reference on template parameter Tuple (and also for the usage of std::tuple_element in the two equal overloads), e.g.

...

template<typename Tuple, int I, typename V>
bool equal(Tuple &&t, 
           V&& v, 
           typename std::enable_if<std::is_same<
             typename std::tuple_element<I, 
               typename std::remove_reference<Tuple>::type>::type,
//             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
             typename std::remove_reference<V>::type>::value>::type* = nullptr)
{
  return std::get<I>(std::forward<Tuple>(t)) == std::forward<V>(v);
}

template<typename Tuple, int I, typename V>
bool equal(Tuple &&t, 
           V&& v,  
           typename std::enable_if<!std::is_same<
             typename std::tuple_element<I,
               typename std::remove_reference<Tuple>::type>::type, 
//             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
             typename std::remove_reference<V>::type>::value>::type* = nullptr)
{
  return false;
}

...

template<typename Tuple, typename V>
int tupleIndexOf(Tuple &&t, V&& v)
{
  return tupleIndexOfWithSeq(std::forward<Tuple>(t), 
                             std::forward<V>(v), 
                             typename MkSequence<std::tuple_size<
                               typename std::remove_reference<Tuple>::type>::value>
//                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                             ::type{});
}

LIVE

Related