I personally think, this should be a minimal-syntax, non-overridable, unary postscript operator that will work for both forward and move, much like cast-to-T&& today

template <typename T> Dog(T&& name) : name(name&&)  //< forward
{}

Name(string&& aName) : name(aName&&)  //< move

{

  cout << "Rvalue Name constructor." << endl;

}

If there is ambiguity with binary operator &&, the latter is chosen
template<class T>
void func(T&& a, int b) {
  a&&-b   //< ok, still operator&&(a,-b)

  (a&&)-b //< ok, std::forward<T>(a) - b;
}


On Mon, Aug 29, 2022 at 2:31 AM Amar Saric via Std-Proposals <std-proposals@lists.isocpp.org> wrote:

Perfect forwarding received a lot of attention initially when it was first added. By now, everybody should be clear on how it is used and what its purpose is. However, the syntax leaves something to be desired, as the forward template requires a parameter, which is actually not necessary, and frankly a bit ugly. This can be completely avoided by stripping the references and using a macro, as follows:

 

#include <iostream>

#include <string>

#include <utility>

 

 

namespace pf

{

template <typename T> struct check_if_ref

{

    static_assert(std::is_reference<T>::value, "ref_forward needs a reference");

    typedef T type;

};

template <typename T> struct _remove_rvalue_reference

{

    typedef T type;

};

template <typename T> struct _remove_rvalue_reference<T &&>

{

    typedef T type;

};

template <typename T>

using remove_rvalue_reference = typename _remove_rvalue_reference<T>::type;

}

#define ref_forward(p)  (std::forward<pf::\

        remove_rvalue_reference<typename pf::\

        check_if_ref<decltype(p)>::type>>(p))

#define ref_to_rvalue(p) (std::move(p))

 

 

using std::cout;

using std::endl;

using std::string;

 

class Name

{

public:

    Name(string& aName) : name(aName)

    {

        cout << "Lvalue Name constructor." << endl;

    }

 

    Name(string&& aName) : name(ref_to_rvalue(aName))

    {

        cout << "Rvalue Name constructor." << endl;

    }

 

    const string& getName() const { return name; }

 

private:

    string name;

};

 

class Dog

{

public:

    template <typename T> Dog(T&& name) : name(ref_forward(name)) {}

    string getName() const { return name.getName(); }

 

private:

    Name name;

};

 

void print(Dog& dog) { cout << "Dog is " << dog.getName() << endl; }

 

int main()

{

    cout << "Dog(string(\"Fido\"))) - rvalue argument:" << endl;

    Dog dog(string("Fido"));

    print(dog);

 

    cout << "Dog(\"Lassie\")) - rvalue const char* argument:" << endl;

    Dog another("Lassie");

    print(another);

 

    string var_name("Woofie");

    cout << "Dog(var_name) - lvalue argument:" << endl;

    Dog yetanother(var_name);

    print(yetanother);

 

    return 0;

}

 

Output:

 

Dog(string("Fido"))) - rvalue argument:

Rvalue Name constructor.

Dog is Fido

Dog("Lassie")) - rvalue const char* argument:

Rvalue Name constructor.

Dog is Lassie

Dog(var_name) - lvalue argument:

Lvalue Name constructor.

Dog is Woofie

 

Providing two macros similar to the ones above in a separate header would be nice to have in my opinion and, considering how may other features have been added over time, maybe others will feel the same. One could argue that this is just a matter of taste, but macros are used in other places as well. It is in a nutshell what I originally expected it to look like back in the day – what I personally find intuitive – and no means not cast in stone.

 

Tell me what you think: Just a hack or is it worth it?

 

Best,

 

Amar

--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals