Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to std::forward( *this )

Tags:

c++

c++14

because I want to overload all cv and reference qualifications of a member function, I wrote myself the following macro:

#define DEFINE_FUNCTION(sig , functionality) \
    sig & { functionality; } \
    sig && { functionality; } \
    sig const & { functionality; } \
    sig const && { functionality; } \
    sig volatile & { functionality; } \
    sig volatile && { functionality; } \
    sig const volatile & { functionality; } \
    sig const volatile && { functionality; }

Unfortunately, If I want to return *this in a forwarding manner (that is preserving all reference and cv-qualification of the this-pointer within the return type, it seems, I have to manually write all 8 of those overloads.

Now to my question: Is it possible to get the cv and reference qualified type of *this in a universal manner?

I have tried decltype(auto) as return type and

return std::forward<decltype(*this)>(*this);

but apparently, the resulting expression always resolves to an lvalue reference, even in case of a &&-qualified function.

Can you help me?

like image 800
Jakob Riedle Avatar asked Nov 06 '17 15:11

Jakob Riedle


1 Answers

What you need is a friend.

struct blah {
  decltype(auto) foo() & { return do_foo(*this); }
  decltype(auto) foo() && { return do_foo(std::move(*this)); }
  decltype(auto) foo() const& { return do_foo(*this); }
  decltype(auto) foo() const&& { return do_foo(std::move(*this)); }
  decltype(auto) foo() const volatile& { return do_foo(*this); }
  decltype(auto) foo() const volatile&& { return do_foo(std::move(*this)); }
  decltype(auto) foo() volatile& { return do_foo(*this); }
  decltype(auto) foo() volatile&& { return do_foo(std::move(*this)); }

  blah( const volatile blah&& b ) {}
  blah( const volatile blah& b ) {}
  blah( const blah& b ) = default;
  blah( blah&& b ) = default;
  blah() = default;

  template<class Self>
  friend blah do_foo(Self&& self) {
    std::cout << "Is reference:" << std::is_reference<Self>::value << "\n";
    std::cout << "Is const:" << std::is_const<std::remove_reference_t<Self>>::value << "\n";
    std::cout << "Is volatile:" << std::is_volatile<std::remove_reference_t<Self>>::value << "\n";
    return decltype(self)(self);
  }
};

test code:

blah{}.foo();
blah tmp;
tmp.foo();
const blah tmp2;
tmp2.foo();

Output:

Is reference:0
Is const:0
Is volatile:0
Is reference:1
Is const:0
Is volatile:0
Is reference:1
Is const:1
Is volatile:0

Live example.

Use the macro to implement the methods that forward to the friend, and write the friend manually. The methods that forward to the friend could even be template<class...Args> if you are ok with the disadvantages of perfect forwarding.

Having less of your code that runs in a macro will be worth it.

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype( __VA_ARGS__ ) \
  { return __VA_ARGS__; }

#define FORWARD_METHOD_TO_FRIEND( NAME, SELF, REF_QUAL, HELPER_NAME ) \
  template<class...Args>
  auto NAME( Args&&... args ) REF_QUAL \
  RETURNS( HELPER_NAME( SELF, std::forward<Args>(args)... ) )

#define FORWARD_CV_METHOD_TO_FRIEND( NAME, SELF, REF_QUAL, HELPER_NAME ) \
  FORWARD_METHOD_TO_FRIEND( NAME, SELF, REF_QUAL, HELPER_NAME ) \
  FORWARD_METHOD_TO_FRIEND( NAME, SELF, const REF_QUAL, HELPER_NAME ) \
  FORWARD_METHOD_TO_FRIEND( NAME, SELF, const volatile REF_QUAL, HELPER_NAME ) \
  FORWARD_METHOD_TO_FRIEND( NAME, SELF, volatile REF_QUAL, HELPER_NAME )

#define FORWARD_SELF_TO_FRIEND( NAME, HELPER_NAME ) \
  FORWARD_CV_METHOD_TO_FRIEND( NAME, *this, &, HELPER_NAME ) \
  FORWARD_CV_METHOD_TO_FRIEND( NAME, std::move(*this), &&, HELPER_NAME ) 

which shortens blah above to:

struct blah {
  FORWARD_SELF_TO_FRIEND( foo, do_foo )
  // ...
  template<class Self>
  void do_foo( Self&& self )
  {
    // some_blah.foo() invokes do_foo passing it
    // self with the proper qualifications
  }
};

replacing (and improving) all of the foo() methods.

some_blah.foo() now invokes do_foo( some_blah ) with proper CV and L/R value qualifications and noexcept on some_blah.

constexpr may require some love to get right, because we don't have a conditional constexpr test in C++ right now. Maybe simply claiming constexpr in the template foo generated is the right thing, but I'm hazy on the subject.

like image 120
Yakk - Adam Nevraumont Avatar answered Oct 17 '22 12:10

Yakk - Adam Nevraumont