Подтвердить что ты не робот

Есть ли причина declval возвращает add_rvalue_reference вместо add_lvalue_reference

изменение типа в ссылку на тип, позволяет получить доступ к членам типа без создания экземпляра типа. Это, по-видимому, верно как для ссылок lvalue, так и для ссылок rvalue.

declval реализуется с помощью add_rvalue_reference вместо add_lvalue_reference, это просто соглашение или есть примеры использования, где add_rvalue_reference является предпочтительным?

Изменить: Полагаю, я был немного расплывчатым, эти ответы очень хороши, но касаются немного разных точек. Предлагаются два разных варианта использования, Говард подчеркнул, что вы можете выбрать, какую ссылку использовать ваш тип, делая add_rvalue_reference более гибким. В других ответах подчеркивается, что поведение по умолчанию автоматически выбирает ссылки, которые более естественно отражают введенный тип. Я не знаю, что выбрать! Если кто-то может добавить два простых примера, мотивируя потребность в каждом свойстве соответственно, тогда я буду удовлетворен.

4b9b3361

Ответ 1

С add_rvalue_reference:

  • declval<Foo>() имеет тип Foo&&.
  • declval<Foo&>() имеет тип Foo& (сведение коллапса: "Foo& &&" сворачивается в Foo&).
  • declval<Foo&&>() имеет тип Foo&& (сведение коллапса: "Foo&& &&" сворачивается в Foo&&).

С add_lvalue_reference:

  • declval<Foo>() будет иметь тип Foo&.
  • declval<Foo&>() будет иметь тип Foo& (сведение коллапса: "Foo& &" сворачивается в Foo&).
  • declval<Foo&&>() будет иметь тип Foo& (!) (сведение коллапса: "Foo&& &" сворачивается в Foo&).

то есть вы никогда не получите Foo&&.

Кроме того, факт, что declval<Foo>() имеет тип Foo&&, хорош (вы можете написать Foo&& rr = Foo();, но не Foo& lr = Foo();). И что declval<Foo&&>() будет иметь тип Foo&, просто чувствует "неправильно"!


Изменить: поскольку вы попросили пример:

#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;

Если declval используется add_lvalue_reference, вы не можете использовать onRvalue() с ним (второй decltype).

Ответ 2

Да, использование add_rvalue_reference дает клиенту возможность указать, хочет ли он объект lvalue или rvalue данного типа:

#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';
}

Какие для меня выходы:

int&&
int&

Ответ 3

Вы хотите получить верную версию T, T& или const/volatile. Поскольку у него может не быть конструктора копирования или перемещения, вы не можете просто вернуть тип, т.е. Нужно вернуть ссылку. С другой стороны, добавление тега rvalue к ссылочному типу не имеет никакого эффекта:

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

То есть, добавление ссылочного типа rvalue приводит к получению результата, который выглядит как объект переданного типа!

Ответ 4

Пример того, где вам нужен контроль над возвращаемым типом, можно найти в моей df.operators библиотеке, когда вам нужно предоставить спецификацию noexcept. Здесь типичный метод:

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

В общем коде вам нужно быть точным о том, что вы делаете. В приведенном выше примере T и U являются типами вне моего контроля и спецификацией noexcept для копии из ссылки const lvalue, ссылка на константу lvalue и ссылку на rvalue могут отличаться. Поэтому я должен иметь возможность выражать такие случаи, как:

  • Можно ли построить T из T&? (Используйте T(std::declval<T&>()))
  • Можно ли построить T из const T&? (Используйте T(std::declval<const T&>()))
  • Можно ли построить T из T&&? (Используйте T(std::declval<T>()))

К счастью, std::declval позволяет использовать вышеописанное с помощью std::add_rvalue_reference и ссылаться на правила сворачивания.