#include <iostream>
#include <utility>

    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);
      }
    };

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