std::get
already does exactly what you want, so your goal is to simply write an alias for an existing function. The trick is to perfect forward your argument and return value to ensure nothing is lost. For example :
#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)
ensure that the return value is perfect forwarded. This preserves the reference qualifier and the constness of the return type. Using auto
would cause the return value to decay to the underlying value type (in this case int
). auto&&
is similarly used to capture the result without discarding reference qualifier or constness.
Edit : It seems there was a type safety component to the question I missed. This is easily fixed by introducing a static_assert
and std::is_same
to compare the argument type with the expected type. It's important to remove reference qualifiers and cv modifiers to ensure the comparison is correct.
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));
}
Unfortunately, the error message will usually be pretty long. Unfortunately I don't see a way to write this where the compiler's built-in argument matching could be used (which would generate clearer error messages). Perfect forwarding requires that the argument be a template type. Otherwise, you would need two overloads (one for const and one for non-const arguments) which would violate the single-function requirement of the question.
If you find it annoying to write out the check for every function, you can write a helper which can be used to more easily write new access functions.
#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));
}
The access function will now fail with a compiler error if the wrong type of tuple is provided :
int main()
{
// Compiler error 'Unexpected tuple type'
std::tuple<double, int> bad_tuple{};
auto&& bad_ref = getFirstElem(bad_tuple);
}