Можно ли написать единственную именованную функцию доступа как для постоянной ссылки, так и для записи ссылки на элементы кортежа?
У меня есть некоторая иерархия std::tuple. Я хотел бы написать некоторые функции доступа для всех этих кортежей, чтобы результирующий код был более читабельным. Поэтому вместо того, чтобы писать:
std::get<2>(std::get<1>(std::get<0>(s)))
Я бы предпочел написать
getNetName(getFirstNet(getFirstOutput(s)))
Теперь важно избежать написания этих функций доступа дважды - для постоянных параметров и для записываемых параметров. Можно ли это сделать? И конечно - я хочу, чтобы эти функции доступа были безопасными для типов. Может быть несколько типов кортежей, к которым может применяться std::get<0>() - но я бы предпочел, чтобы getNetName() создавала ошибку компилятора, если она не применяется к сети.
2 ответа
Извините, это 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;
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));
}
К сожалению, сообщение об ошибке обычно будет довольно длинным. К сожалению, я не вижу способа написать это, где можно было бы использовать встроенное сопоставление аргументов компилятора (которое генерировало бы более четкие сообщения об ошибках). Идеальная пересылка требует, чтобы аргумент был типом шаблона. В противном случае вам потребуются две перегрузки (одна для const и одна для неконстантных аргументов), что нарушит требование отдельной функции вопроса.
Если вас раздражает выписывать проверку для каждой функции, вы можете написать помощника, который может быть использован для более простого написания новых функций доступа.
#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);
}