Есть ли причина declval возвращает add_rvalue_reference вместо add_lvalue_reference
изменение типа в ссылку на тип, позволяет получить доступ к членам типа без создания экземпляра типа. Это, по-видимому, верно как для ссылок lvalue, так и для ссылок rvalue.
declval реализуется с помощью add_rvalue_reference
вместо add_lvalue_reference
, это просто соглашение или есть примеры использования, где add_rvalue_reference
является предпочтительным?
Изменить:
Полагаю, я был немного расплывчатым, эти ответы очень хороши, но касаются немного разных точек. Предлагаются два разных варианта использования, Говард подчеркнул, что вы можете выбрать, какую ссылку использовать ваш тип, делая add_rvalue_reference
более гибким. В других ответах подчеркивается, что поведение по умолчанию автоматически выбирает ссылки, которые более естественно отражают введенный тип. Я не знаю, что выбрать! Если кто-то может добавить два простых примера, мотивируя потребность в каждом свойстве соответственно, тогда я буду удовлетворен.
Ответы
Ответ 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
и ссылаться на правила сворачивания.