Можно ли написать одну именованную функцию доступа как для постоянной ссылки, так и для записываемой ссылки на элементы кортежа?

У меня есть некоторая иерархия std::tuple. Я хотел бы написать некоторые функции доступа для всех этих кортежей, чтобы полученный код был более читаемым. Поэтому вместо того, чтобы писать:

std::get<2>(std::get<1>(std::get<0>(s)))

Я бы предпочел написать

getNetName(getFirstNet(getFirstOutput(s)))

Теперь дело в том, чтобы не писать эти функции доступа дважды — для постоянных параметров и для записываемых параметров. Можно ли это сделать? И, конечно же, я хочу, чтобы эти функции доступа были типобезопасными. Может быть несколько типов кортежей, к которым можно применить std::get‹0>(), но я бы предпочел, чтобы getNetName() создавал ошибку компилятора, если он не применяется к сети.


person Community    schedule 10.07.2018    source источник
comment
Если вы используете геттеры и сеттеры, ваш геттер не должен позволять вам изменять состояние, чтобы вы всегда могли вернуться по значению или const&.   -  person NathanOliver    schedule 10.07.2018
comment
Это кажется довольно запутанным подходом к обходному пути использования кортежей вместо реальных классов. Почему бы не избежать проблем и не сделать код типобезопасным, легко читаемым и более идиоматичным при небольших затратах на написание нескольких тривиальных классов?   -  person Voo    schedule 10.07.2018
comment
Кортеж — это структура с безымянными элементами. Так в чем смысл использования кортежа и написания дополнительных функций для получения именованного доступа? Это кажется полной противоположностью смыслу кортежа.   -  person Klaus    schedule 10.07.2018
comment
Ребята, если эта проблема (и решение) не оценят, я ее удалю.   -  person    schedule 11.07.2018


Ответы (2)


std::get уже делает именно то, что вы хотите, поэтому ваша цель — просто написать псевдоним для существующей функции. Хитрость заключается в том, чтобы усовершенствовать ваш аргумент и возвращаемое значение, чтобы ничего не было потеряно. Например :

#include <tuple>

template<class T>
decltype(auto) getFirstElem(T&& p_tuple)
{
    return std::get<0>(std::forward<T>(p_tuple));
}

int main()
{
    std::tuple<int, int> mutable_x = { 42, 24 };
    const std::tuple<int, int> const_x = { 10, 20 };

    // mutable_ref is a `int&`
    auto&& mutable_ref = getFirstElem(mutable_x);

    // const_ref is a `const int&`
    auto&& const_ref = getFirstElem(const_x);
}

decltype(auto) убедитесь, что возвращаемое значение идеально перенаправлено. Это сохраняет квалификатор ссылки и константность возвращаемого типа. Использование auto приведет к уменьшению возвращаемого значения до базового типа значения (в данном случае int). auto&& аналогичным образом используется для захвата результата без отбрасывания квалификатора ссылки или константы.

Изменить. Кажется, в вопросе, который я пропустил, был компонент безопасности типов. Это легко исправить, введя static_assert и std::is_same для сравнения типа аргумента с ожидаемым типом. Важно удалить ссылочные квалификаторы и модификаторы cv, чтобы обеспечить правильность сравнения.

template<class T>
decltype(auto) getFirstElem(T&& p_tuple)
{
    using t_expected = std::tuple<int, int>;

    // Verify that the tuple matches the expectations
    using t_tuple_clean = std::remove_cv_t<std::remove_reference_t<T>>;
    static_assert(std::is_same<t_expected, t_tuple_clean>::value, "Unexpected tuple type");

    return std::get<0>(std::forward<T>(p_tuple));
}

К сожалению, сообщение об ошибке обычно будет довольно длинным. К сожалению, я не вижу способа написать это, где можно было бы использовать встроенное в компилятор сопоставление аргументов (что генерировало бы более четкие сообщения об ошибках). Для идеальной пересылки требуется, чтобы аргумент был шаблонным типом. В противном случае вам понадобятся две перегрузки (одна для константных и одна для неконстантных аргументов), которые нарушат однофункциональное требование вопроса.

Если вас утомляет выписывание проверки для каждой функции, вы можете написать вспомогательную функцию, которую можно использовать для упрощения написания новых функций доступа.

#include <tuple>
#include <type_traits>

template<size_t index, class t_expected, class t_tuple>
decltype(auto) getHelper(t_tuple&& p_tuple)
{
    // Verify that the tuple matches the expectations
    using t_tuple_clean = std::remove_cv_t<std::remove_reference_t<t_tuple>>;
    static_assert(std::is_same<t_expected, t_tuple_clean>::value, "Unexpected tuple type");

    // Forward to std::get
    return std::get<index>(std::forward<t_tuple>(p_tuple));
}


template<class T>
decltype(auto) getFirstElem(T&& p_tuple)
{
    return getHelper<0, std::tuple<int, int>>(std::forward<T>(p_tuple));
}

Функция доступа теперь завершится с ошибкой компилятора, если будет предоставлен неправильный тип кортежа:

int main()
{
    // Compiler error 'Unexpected tuple type'
    std::tuple<double, int> bad_tuple{};
    auto&& bad_ref = getFirstElem(bad_tuple);
}
person François Andrieux    schedule 10.07.2018
comment
не безопасный тип. getFirstElem() может применяться к любому типу кортежа с более чем 0 элементами. Не хватает сути. - person ; 10.07.2018
comment
Возможно, вам следует уточнить, что вы подразумеваете под безопасностью типов в вопросе. Например, что такое сетка? Это форма кортежа? Тип одного из элементов кортежа? Что-то другое? - person Useless; 10.07.2018
comment
@FrankPuck Честно говоря, я пропустил эту часть вопроса. Я обновлю свой ответ. - person François Andrieux; 10.07.2018
comment
@FrankPuck Вы пытаетесь ограничить типы кортежей, которые передаются в качестве аргументов, или вы пытаетесь ограничить типы ввода теми, которые имеют ожидаемые типы элементов по данному индексу? - person François Andrieux; 10.07.2018
comment
getNetName() должен компилироваться только в том случае, если аргумент является сетью - person ; 10.07.2018
comment
@FrankPuck Чтобы было ясно, если сеть является типом tuple, ее невозможно отличить от других tuple с теми же типами членов. В этом случае безопасность типов может дать вам только то, что нужно. - person François Andrieux; 10.07.2018
comment
@FrankPuck в этом случае решение состоит в том, чтобы сделать net типом класса со значимыми членами, а не псевдонимом для конкретного экземпляра std::tuple - person Caleth; 10.07.2018

извините, это C++11 (пожаловаться моему боссу)!

template<typename T, std::size_t I>
struct get
{       auto operator()(T &_r) const -> decltype(std::get<I>(_r))
        {       return std::get<I>(_r);
        }
        auto operator()(const T &_r) const -> decltype(std::get<I>(_r))
        {       return std::get<I>(_r);
        }
};

и приложение:

typedef std::pair<
    std::size_t, // starting value
    std::size_t // size or ending value?
> dimension;
typedef get<dimension, 0> getDimensionStart;
typedef get<dimension, 1> getDimensionSize;
typedef std::pair<
    std::string,
    boost::optional<dimension>      // if scalar, then this is empty
> port;
typedef get<port, 0> getPortName;
typedef get<port, 1> getPortDimension;

использование этого кода будет выглядеть так:

const port s("name", dimension(0, 10));
std::cout << getDimensionStart()(*getPortDimension()(s)) << std::endl;
person Community    schedule 10.07.2018