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

template <typename T>
struct Demo {
    T x;
    T y;
};

И я пытаюсь написать обобщенную функцию, аналогичную std::get для кортежей, которая принимает индекс времени компиляции I и возвращает ссылку на lvalue на I -ый член структуры if он вызывается с 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?

9
Corristo 27 Май 2017 в 20:37

2 ответа

Лучший ответ

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

decltype, примененный к структурированной привязке, возвращает ссылочный тип, который для простой структуры является объявленным типом упомянутого члена данных (но украшенного cv-квалификаторами завершенного объекта), а для кортежа case - это то, что "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
T.C. 27 Май 2017 в 19:29

Вот общее решение, чтобы получить то, что вы хотите. Это вариация 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
Oktalist 28 Май 2017 в 18:34