Идеальная пересылка переменных, объявленных со структурной привязкой

У меня есть структура

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?

8

Решение

Поведение правильное.

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

Другие решения

Вот общее решение, чтобы получить то, что вы хотите. Это вариация на 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);
}
}

DEMO

4

По вопросам рекламы [email protected]