You need 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
Living example .
Use a macro to implement methods that are forwarded to a friend, and write each other manually. The methods that are passed to a friend may even be template<class...Args> if you are ok with the disadvantages of perfect forwarding.
You will have less of your code that works in a macro.
#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 to:
struct blah { FORWARD_SELF_TO_FRIEND( foo, do_foo )
replaces (and improves) all foo() methods.
some_blah.foo() now calls do_foo( some_blah ) with the proper CV and L / R qualifications and noexcept on some_blah .
constexpr may take some love to get right, because we don't have a constexpr conditional test in C ++ right now. Perhaps the simple constexpr statement in the foo template is generated correctly, but I am foggy on this.