简体   繁体   中英

How to create a std::forward-like returning const & for anything that is not an lref

Long story for this question at the end. What is the easiest way to create a function behaving like std::forward but returning an lref for everything that is an lref and a const & ref in all other cases? Eg with the following "conversion" table:

arg myforward(arg)
T& T&
T&& const T&
const T& const T&
const T&& const T&

My main question is why the obvious method is not working for me (having four function accepting T& , T&& , const T& , const T&& with only the first returning a T& . Code I'm currently using (note, helper for monitoring creation and deletion of objects and printing types at the end):

#include <iostream>

using namespace std;

// Implementation #1
template <typename T> T & flr1(T & value) {/* std::cout<<"  picked flr1 &"<<endl;*/ return value;};
template <typename T> const T &flr1(T && value) {/* std::cout<<"  picked flr1 &&"<<endl; */return static_cast<const T&>(value);};
template <typename T> const T & flr1(const T & value) {/* std::cout<<"  picked flr1 const &"<<endl; */return static_cast<const T&>(value);};
template <typename T> const T & flr1(const T && value) {/* std::cout<<"  picked flr1 const &&"<<endl; */return static_cast<const T&>(value);};

// Implementation #2 (same results as #1)
//template<typename T> using myremove_cref_t = typename remove_const<typename remove_reference<T>::type>::type;
//template <typename T> myremove_cref_t<T> & flr2(myremove_cref_t<T>  & value) { return value;};
//template <typename T> const myremove_cref_t<T> &flr2(myremove_cref_t<T>  && value) { return value;};
//template <typename T> const myremove_cref_t<T> & flr2(const myremove_cref_t<T>   & value) { return value;};
//template <typename T> const myremove_cref_t<T> & flr2(const myremove_cref_t<T>   && value) { return value;};

// Implementation #3
template <typename T> struct forwardLRefOrConst { typedef T &type; static const char *name() {return "generic";}};
template <typename T> struct forwardLRefOrConst<T&> { typedef T &type; static const char *name() {return "T&";}};
template <typename T> struct forwardLRefOrConst<T &&> { typedef const T &type; static const char *name() {return "T &&";}};
template <typename T> struct forwardLRefOrConst<const T &> { typedef const T &type; static const char *name() {return "const T&";}};
template <typename T> using forwardLRefOrConst_t = typename forwardLRefOrConst<T>::type;
template <typename T> forwardLRefOrConst_t<T> flr3(T &value) { return static_cast<forwardLRefOrConst_t<T>>(value); }

// Test code
template <typename T> void test2(const char *name, T && arg)
{
  cout<<"  test "<<name<<" is "<<Name<decltype(arg)>::name()<<endl;
}
template <typename T> void test(T &&arg)
{
  cout<<"  test arg is "<<Name<decltype(arg)>::name()<<endl;
  test2("flr1(arg)",flr1<T>(arg)); // Implementation 1
  test2("flr1(std::forward(arg))",flr1<T>(std::forward<T>(arg))); // Implementation 1 bis
  test2("flr3(arg)",flr3<decltype(arg)>(arg)); // Implementation 3
}

int main()
{
  cout<<"Initialization"<<endl;
  ClassA a1;
  const ClassA a2;
  ClassA &a3(a1);
  ClassA &&a4=ClassA();
  const ClassA &&a5=ClassA();
  cout<<("Test (T)")<<endl;
  test(a1);
  cout<<("Test (const T)")<<endl;
  test(a2);
  cout<<("Test (T &)")<<endl;
  test(a3);
  cout<<("Test (T &&)")<<endl;
  test(std::forward<ClassA>(a4));
  cout<<("Test (immediate)")<<endl;
  test(ClassA());
  cout<<("Test (const T&&)")<<endl;
  test(a5);
  cout<<"End"<<endl;

  return 0;
}

This program generates the following output

Initialization
  Constructed A1
  Constructed A2
  Constructed A3
  Constructed A4
Test (T)
  test arg is &T
  test flr1(arg) is &T
  test flr1(std::forward(arg)) is &T
  test flr3(arg) is &T
Test (const T)
  test arg is const &T
  test flr1(arg) is const &T
  test flr1(std::forward(arg)) is const &T
  test flr3(arg) is const &T
Test (T &)
  test arg is &T
  test flr1(arg) is &T
  test flr1(std::forward(arg)) is &T
  test flr3(arg) is &T
Test (T &&)
  test arg is &&T
  test flr1(arg) is &T
  test flr1(std::forward(arg)) is const &T
  test flr3(arg) is const &T
Test (immediate)
  Constructed A5
  test arg is &&T
  test flr1(arg) is &T
  test flr1(std::forward(arg)) is const &T
  test flr3(arg) is const &T
  Destroyed A5
Test (const T&&)
  test arg is const &T
  test flr1(arg) is const &T
  test flr1(std::forward(arg)) is const &T
  test flr3(arg) is const &T
End
  Destroyed A4
  Destroyed A3
  Destroyed A2
  Destroyed A1

As visible above only flr1<T>(std::forward<T>(arg)) or flr3<decltype(arg)>(arg) give the expected result but they are both too verbose for my taste. I would like to have a simple function myforward(arg) or at worst myfoward<T>(arg) . Is there any way to achieve that?

/* Test class for arguments */
static int index=1;
template <int I> struct TestClass
{
  TestClass(int val): val(val)
  {
    cout<<"  Constructed "<<id()<<val<<endl;
    if(index<=val)
      index=val+1;
  }
  TestClass(): val(index++)
  {
    cout<<"  Constructed "<<id()<<val<<endl;
  }
  TestClass(const TestClass<I> &value): val(index++)
  {
    cout<<"  Constructed from "<<id()<<value.val<<" to "<<id()<<val<<endl;
  }
  TestClass(TestClass<I> &&value): val(index++)
  {
    cout<<"  Moved "<<id()<<value.val<<" to "<<id()<<val<<endl;
    val=value.val;
    value.val=0;
  }
  void operator =(TestClass &&value)
  {
    cout<<"  Assign moved "<<id()<<value.val<<" to "<<id()<<val<<endl;
    val=value.val;
    value.val=0;
  }
  void operator =(const TestClass &value)
  {
    cout<<"  Assign copied "<<id()<<value.val<<" to "<<id()<<val<<endl;
  }
  ~TestClass()
  {
    if(val)
      cout<<"  Destroyed "<<id()<<val<<endl;
  }
  char id() const {return 'A'+I;}
  int val;
};
typedef TestClass<0> ClassA;
typedef TestClass<1> ClassB;

/* Functions to print a data type */
template <typename T> struct Name
{
  static std::string name()
  {
    std::string ret;
    if(std::is_const<T>())
      ret="const ";
    if(std::is_lvalue_reference<T>())
      ret+=Name<typename std::remove_reference<T>::type>::name();
    else if(std::is_rvalue_reference<T>())
      ret+=Name<typename std::remove_reference<T>::type>::name();
    else
      ret+="T";
    return ret;
  }
};

template <typename T> struct Name<T &>
{
  static std::string name()
  {
    std::string ret;
    if(std::is_const<T>())
      ret="const ";
    ret+="&T";
    return ret;
  }
};

template <typename T> struct Name<T &&>
{
    static std::string name()
    {
      std::string ret;
      if(std::is_const<T>())
        ret="const ";
      ret+="&&T";
      return ret;
    }
};

I'm currently creating a small C++/Qt wrapper for Sqlite. I've written in the Query all the overloads for binding:

bool bind(int col, int value);
bool bind(int col, double value);

and the ones for fetching:

bool columnSingle(int col, int &value);
bool columnSingle(int col, double &value);

Together with its parameter pack variants:

template <typename ...T> bool bindMany(int startCol, const T &...values);
template <typename ...T> bool fetchMany(int startCol, T &...values);

and its version working with tuples.

Now I'm currently working on a function that performs in a single operation the bind of N parameters, a single step and the fetch of M columns:

template <int N, typename ...T> bool querySingle(T &&...values);

In its body I transform values to a tuple (Let's call it A) and then create from it two tuples: The first containing the first N values and the other the remaining ones. The problem is that if I create it with std::forward_as_tuple the fetching part will work even if I pass a immediate instead of throwing an error as expected.

eg

querySingle<2>(4, "Foo", 5 /* This should give an error, all parameters after the 2nd should be an lref */ , a);

I was thus trying to create a forward function behaving as described above.

I would simply do

template <typename T>
decltype(auto) myforward(T&& value)
{
    if constexpr (std::is_rvalue_reference_v<T&&>
                  or std::is_const_v<std::remove_reference_t<T>>) {
        return static_cast<const T&>(value);
    } else {
        return static_cast<T&>(value);
    }
}

Demo

As visible above only flr1<T>(std::forward<T>(arg)) or flr3<decltype(arg)>(arg) give the expected result

Notice that inside the function:

template <typename T> void test(T&& arg)

arg has name and is so a l-value. To keep/retrieve its original form , you have either to std::forward<T>(arg) or provide T to your template and work accordingly:

template <typename T, typename U>
decltype(auto) myforward2(U&& value)
{
    if constexpr (std::is_rvalue_reference_v<T&&>
                  or std::is_const_v<std::remove_reference_t<T>>) {
        return static_cast<const T&>(value);
    } else {
        return static_cast<T&>(value);
    }
}

template <typename T>
decltype(auto) test(T&& arg)
{
    return myforward2<T>(arg); // arg is l-value here
}

Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM