If I have an object a
that either a built-in array or a class-type with a suitable operator []
, and its returned type can be indexed itself, how should I write a generic function that can index all of them with a variadic call instead of separated bracket block? In other words, I can make expressions like:
a[i0][i1]...[iK]
and I want to be able to write it as a single function:
slice( a, i0, i1, ..., iK )
since the rules of C++ require operator []
to work on single arguments, making it less-compatible with variadic stuff. (This question is based on a Usenet thread where I tried asking something similar; ending up solving only possibly-nested built-in arrays myself.)
A first stab:
template < typename T, typename U >
constexpr
auto slice( T &&t, U &&u ) noexcept(???) -> ???
{ return ce_forward<T>(t)[ ce_forward<U>(u) ]; }
template < typename T, typename U, typename V, typename ...W >
constexpr
auto slice( T &&t, U &&u, V &&v, W... &&w ) noexcept(???) -> ???
{
return slice( ce_forward<T>(t)[ce_forward<U>(u)], ce_forward<V>(v),
ce_forward<W>(w)... );
}
The ce_forward
function templates are a constexpr
-marked std::forward
. (Some things in the standard that could be marked constexpr
aren't.) I'm trying to figure out the right stuff to put in the return type and exception specification spots. Some cases and caveats I know of are:
operator []
requires one operand to be a data-pointer (or decayed array reference) and the other to be an enumeration or integer type. The operands can be in either order. I don't know if an operand could be replaced with a class-type with an unambiguous (non-explicit?) conversion to an appropriate type.operator []
as non-static member function(s). Any such function can only have one parameter (besides this
).slice
.std::is_nothrow_move_constructible<ReturnType>::value
should be OR'd to the exception specification. (By-reference returns are noexcept
.)this
(const
/volatile
/both/neither and/or &
/&&
/neither).I've come up with a solution, based on the comment by @luc-danton and answer by @user315052.
#include <utility>
template < typename Base, typename ...Indices >
class indexing_result;
template < typename T >
class indexing_result<T>
{
public:
using type = T;
static constexpr
bool can_throw = false;
};
template < typename T, typename U, typename ...V >
class indexing_result<T, U, V...>
{
using direct_type = decltype( std::declval<T>()[std::declval<U>()] );
using next_type = indexing_result<direct_type, V...>;
static constexpr
bool direct_can_throw
= not noexcept( std::declval<T>()[std::declval<U>()] );
public:
using type = typename next_type::type;
static constexpr
bool can_throw = direct_can_throw || next_type::can_throw;
};
template < typename T >
inline constexpr
auto slice( T &&t ) noexcept -> T &&
{ return static_cast<T &&>(t); }
template < typename T, typename U, typename ...V >
inline constexpr
auto slice( T &&t, U &&u, V &&...v )
noexcept( !indexing_result<T, U, V...>::can_throw )
-> typename indexing_result<T, U, V...>::type
{
return slice( static_cast<T &&>(t)[static_cast<U &&>( u )],
static_cast<V &&>(v)... );
}
I put a full example program up as a Gist. It worked on a web-site compiler running GCC >= 4.7, CLang >= 3.2, and Intel C++ >= 13.0.
To get the right return value for the slice
function, you can create a helper template that computes the correct result type. For example:
template <typename T, typename U, typename... V>
struct SliceResult {
typedef typename SliceResult<T, V...>::Type Type;
};
template <typename T, typename U>
struct SliceResult<T, U> {
typedef typename std::underlying_type<T[U(0)]>::type Type;
};
The slice
function would then return a SliceResult<...>::Type
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With