5

如果我有a一个内置数组或具有合适的类类型的对象operator [],并且它的返回类型可以自己索引,我应该如何编写一个通用函数,可以通过可变参数调用而不是分隔来索引所有这些对象括号块?换句话说,我可以表达如下:

a[i0][i1]...[iK]

我希望能够把它写成一个函数:

slice( a, i0, i1, ..., iK )

因为 C++ 的规则需要operator []处理单个参数,这使得它与可变参数的东西不太兼容。(这个问题基于我尝试询问类似问题的 Usenet 线程;最终我自己只解决了可能嵌套的内置数组。)

第一次刺:

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)... );
}

ce_forward功能模板是-constexpr标记的std::forward。(标准中可以标记constexpr的一些东西不是。)我试图找出正确的东西来放入返回类型和异常规范点。我知道的一些案例和注意事项是:

  • 内置operator []要求一个操作数是数据指针(或衰减数组引用),另一个是枚举或整数类型。操作数可以是任意顺序。我不知道是否可以将操作数替换为具有明确(非显式?)转换为适当类型的类类型。
  • 类类型可以定义operator []为非静态成员函数。任何这样的函数只能有一个参数(除了this)。
  • 内置运算符不能抛出。(除非操作数可以基于 UDT 转换;所述转换是唯一可能的抛出点。)由边界错误引起的未定义行为超出了我们的权限范围。
  • 最后一次索引调用是否返回左值引用、右值引用或值应该反映在slice.
  • 如果(最终?)调用是按值调用,那么std::is_nothrow_move_constructible<ReturnType>::value应该对异常规范进行 OR'd。(按引用返回是noexcept。)
  • 如果内置运算符涉及一个数组,则该步骤的返回引用应该是一个右值引用,如果该数组也是一个。(这是一种缺陷,因为与数组不同,指针会丢失其目标的左值与右值状态,因此传统的返回始终是左值引用。)
  • 对于类类型索引运算符,请确保 exception-spec 和 return-type 部分引用函数体使用的相同重载(如果不止一个)。this请注意仅在( const/ volatile/both/neither 和/或&/ &&/neither)的限定方面不同的重载。
4

2 回答 2

3

根据@luc-danton 的评论和@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)... );
}

我提出了一个完整的示例程序作为 Gist。它在运行 GCC >= 4.7、CLang >= 3.2 和 Intel C++ >= 13.0 的网站编译器上工作。

于 2013-04-03T00:22:09.877 回答
1

要获得slice函数的正确返回值,您可以创建一个帮助模板来计算正确的结果类型。例如:

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;
};

然后该slice函数将返回一个SliceResult<...>::Type.

于 2012-06-04T07:59:09.367 回答