У меня есть структура
template <typename T>
struct Demo {
T x;
T y;
};
и я пытаюсь написать общую функцию, аналогичную std::get
для кортежей, которые принимают индекс времени компиляции I
и возвращает ссылку lvalue на I
-ый член структуры, если он вызывается с lvalue DemoStruct<T>
и Rvalue ссылка на I
-ый член структуры, если он вызывается с rvalue DemoStruct<T>
,
Моя текущая реализация выглядит так
template <size_t I, typename T>
constexpr decltype(auto) struct_get(T&& val) {
auto&& [a, b] = std::forward<T>(val);
if constexpr (I == 0) {
return std::forward<decltype(a)>(a);
} else {
return std::forward<decltype(b)>(b);
}
}
Однако, это не делает то, что я ожидал, и всегда возвращает rvalue-ссылку на T
вместо.
Вот это wandbox, который показывает проблему.
Как правильно вернуть ссылки на члены структуры, сохраняя категорию значений структуры, переданной в функцию?
РЕДАКТИРОВАТЬ:
Как отметил Кинан Аль Сармини, auto&& [a, b] = ...
действительно выводит типы для a
а также b
быть не ссылочными типами. Это также верно для std::tuple
например, и то и другое
std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}};
auto&& [a, b] = my_tuple;
static_assert(!std::is_reference_v<decltype(a)>);
а также
std::tuple my_tuple{std::string{"foo"}, std::string{"bar"}};
auto&& [a, b] = std::move(my_tuple);
static_assert(!std::is_reference_v<decltype(a)>);
компилировать нормально, хотя std::get<0>(my_tuple)
возвращает ссылки, как показано
std::tuple my_tuple{3, 4};
static_assert(std::is_lvalue_reference_v<decltype(std::get<0>(my_tuple))>);
static_assert(std::is_rvalue_reference_v<decltype(std::get<0>(std::move(my_tuple)))>);
Это языковой дефект, намеренный или ошибка как в GCC, так и в Clang?
Поведение правильное.
decltype
применительно к структурированной привязке возвращает ссылочный тип, который для простой структуры является объявленным типом элемента данных, на который ссылаются (но украшенного cv-квалификаторами завершенного объекта), а для кортежа типа «все tuple_element
возвращается для этого элемента «. Это примерно моделирует поведение decltype
применительно к доступу простого члена класса.
В настоящее время я не могу думать ни о чем, кроме как вручную вычислить желаемый тип, т.е.
using fwd_t = std::conditional_t<std::is_lvalue_reference_v<T>,
decltype(a)&,
decltype(a)>;
return std::forward<fwd_t>(a);
Вот общее решение, чтобы получить то, что вы хотите. Это вариация на std::forward
это позволяет аргументу шаблона и аргументу функции иметь несвязанные типы, и условно преобразует аргумент функции в значение r, если его аргумент шаблона не является ссылкой на lvalue.
template <typename T, typename U>
constexpr decltype(auto) aliasing_forward(U&& obj) noexcept {
if constexpr (std::is_lvalue_reference_v<T>) {
return obj;
} else {
return std::move(obj);
}
}
Я назвал это aliasing_forward
как дань «псевдониму конструктора» std::shared_ptr
,
Вы можете использовать это так:
template <size_t I, typename T>
constexpr decltype(auto) struct_get(T&& val) {
auto&& [a, b] = val;
if constexpr (I == 0) {
return aliasing_forward<T>(a);
} else {
return aliasing_forward<T>(b);
}
}