Is there a reason declval returns add_rvalue_reference instead of add_lvalue_reference

前端 未结 4 1400
无人及你
无人及你 2020-12-13 18:18

changing a type into a reference to a type, allows one to access the members of the type without creating an instance of the type. This seems to be true for bot

相关标签:
4条回答
  • 2020-12-13 18:48

    You want to be able to get back aT, a T&, or const/volatile qualified versions thereof. Since may not have a copy or move constructor, you can't just return the type, i.e., a reference needs to be returned. On the other hand, adding an rvalue teference to a reference type has no effect;

     std::declval<T>  -> T&&
     std::declval<T&> -> T&
    

    That is, adding an rvalue reference type has the effect of yielding a result which looks like an object of the passed type!

    0 讨论(0)
  • 2020-12-13 18:52

    Yes, the use of add_rvalue_reference gives the client the choice of specifying whether he wants an lvalue or rvalue object of the given type:

    #include <type_traits>
    #include <typeinfo>
    #include <iostream>
    #ifndef _MSC_VER
    #   include <cxxabi.h>
    #endif
    #include <memory>
    #include <string>
    #include <cstdlib>
    
    template <typename T>
    std::string
    type_name()
    {
        typedef typename std::remove_reference<T>::type TR;
        std::unique_ptr<char, void(*)(void*)> own
               (
    #ifndef _MSC_VER
                    abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                               nullptr, nullptr),
    #else
                    nullptr,
    #endif
                    std::free
               );
        std::string r = own != nullptr ? own.get() : typeid(TR).name();
        if (std::is_const<TR>::value)
            r += " const";
        if (std::is_volatile<TR>::value)
            r += " volatile";
        if (std::is_lvalue_reference<T>::value)
            r += "&";
        else if (std::is_rvalue_reference<T>::value)
            r += "&&";
        return r;
    }
    
    int
    main()
    {
        std::cout << type_name<decltype(std::declval<int>())>() << '\n';
        std::cout << type_name<decltype(std::declval<int&>())>() << '\n';
    }
    

    Which for me outputs:

    int&&
    int&
    
    0 讨论(0)
  • 2020-12-13 19:02

    With add_rvalue_reference:

    • declval<Foo>() is of type Foo&&.
    • declval<Foo&>() is of type Foo& (reference collapsing: “Foo& &&” collapses to Foo&).
    • declval<Foo&&>() is of type Foo&& (reference collapsing: “Foo&& &&” collapses to Foo&&).

    With add_lvalue_reference:

    • declval<Foo>() would be of type Foo&.
    • declval<Foo&>() would be of type Foo& (reference collapsing: “Foo& &” collapses to Foo&).
    • declval<Foo&&>() would be of type Foo& (!) (reference collapsing: “Foo&& &” collapses to Foo&).

    that is, you would never get a Foo&&.

    Also, the fact that declval<Foo>() is of type Foo&& is fine (you can write Foo&& rr = Foo(); but not Foo& lr = Foo();). And that declval<Foo&&>() would be of type Foo& just feels “wrong”!


    Edit: Since you asked for an example:

    #include <utility>
    using namespace std;
    
    struct A {};
    struct B {};
    struct C {};
    
    class Foo {
    public:
        Foo(int) { } // (not default-constructible)
    
        A onLvalue()   &  { return A{}; }
        B onRvalue()   && { return B{}; }
        C onWhatever()    { return C{}; }
    };
    
    decltype( declval<Foo& >().onLvalue()   ) a;
    decltype( declval<Foo&&>().onRvalue()   ) b;
    decltype( declval<Foo  >().onWhatever() ) c;
    

    If declval used add_lvalue_reference you couldn't use onRvalue() with it (second decltype).

    0 讨论(0)
  • 2020-12-13 19:03

    An example of where you need control over the returned type can be found in my df.operators library, when you need to provide a noexcept specification. Here's a typical method:

    friend T operator+( const T& lhs, const U& rhs )
      noexcept( noexcept( T( lhs ),
                          std::declval< T& >() += rhs,
                          T( std::declval< T& >() ) ) )
    {
        T nrv( lhs );
        nrv += rhs;
        return nrv;
    }
    

    In generic code, you need to be exact about what you are doing. In the above, T and U are types outside of my control and the noexcept specification for a copy from a const lvalue reference, a non-const lvalue reference and an rvalue reference could be different. I therefore need to be able to express cases like:

    • Can I construct T from a T&? (Use T(std::declval<T&>()))
    • Can I construct T from a const T&? (Use T(std::declval<const T&>()))
    • Can I construct T from a T&&? (Use T(std::declval<T>()))

    Luckily, std::declval allows the above by using std::add_rvalue_reference and reference the collapsing rules.

    0 讨论(0)
提交回复
热议问题