Есть ли причина, по которой declval возвращает add_rvalue_reference вместо add_lvalue_reference
Изменение типа в reference
к типу, позволяет получить доступ к членам типа, не создавая экземпляр типа. Похоже, что это верно для обоих lvalue references
а также rvalue references
,
declval
реализуется с add_rvalue_reference
вместо add_lvalue_reference
,
- это просто соглашение,
- или есть примеры использования где
add_rvalue_reference
предпочтительнее?
Редактировать: я полагаю, я был немного расплывчатым, все эти ответы очень хорошие, но затрагивают несколько разные моменты. Предлагается использовать два разных ответа. Говард подчеркнул, что вы можете выбрать, какую ссылку использовать в вашем типе, add_rvalue_reference
более гибкий. Другие ответы подчеркивают, что поведение по умолчанию автоматически выбирает ссылки, которые более естественно отражают тип ввода. Я не знаю что выбрать! Если бы кто-то мог добавить два простых примера, мотивирующих необходимость каждого свойства соответственно, то я был бы удовлетворен.
4 ответа
С 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
).
Да, использование 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&
Вы хотите иметь возможность получить обратноT
, T&
, или же const
/volatile
квалифицированные версии их. Поскольку может не иметь конструктора копирования или перемещения, вы не можете просто вернуть тип, т. Е. Ссылка должна быть возвращена. С другой стороны, добавление параметра rvalue к ссылочному типу не имеет никакого эффекта;
std::declval<T> -> T&&
std::declval<T&> -> T&
То есть добавление ссылочного типа rvalue приводит к получению результата, который выглядит как объект переданного типа!
Пример того, где вам нужен контроль над возвращаемым типом, можно найти в моей библиотеке 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
спецификация для копии из константной 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
и сослаться на сворачивающиеся правила.