15

问题

让我们假设一个函数func接受表单中的任何容器Container<Type, N, Args...>(即一个容器,它将第一个模板参数作为一个类型,作为第二个std::size_t定义容器中有多少个参数)并返回其i第 th 个元素当且仅当N介于4042.

这种容器的一个例子是std::array.

我的第一个版本的函数将是:

template
    < template<class, std::size_t, class...> class Container
    , class Type
    , std::size_t N
    , class... Args >
auto func(std::size_t i, Container<Type, N, Args...>& container) -> decltype(container[0]) { 
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return container[i];
}

然后我需要一个const重载:

template
    < template<class, std::size_t, class...> class Container
    , class Type
    , std::size_t N
    , class... Args >
auto func(std::size_t i, const Container<Type, N, Args...>& container) -> decltype(container[0]) { 
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return container[i];
}

问题

是否可以定义类似的东西(这不起作用,因为这不是通用参考):

template
    < template<class, std::size_t, class...> class Container
    , class Type
    , std::size_t N
    , class... Args >
auto func(std::size_t i, Container<Type, N, Args...>&& container) -> decltype(container[0]) { 
    //                                              ^^
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return container[i];
}

为了定义这个函数的单一版本,并且 make 对两者都Container<Type, N, Args...>&有效const Container<Type, N, Args...>&

4

6 回答 6

7

如果不实际使用通用引用,您将无法获得“通用引用”的优势,因此只需将Container其设为“通用引用”参数。如果您这样做,您需要做的就是使用另一种技术来查找N.

一种选择是简单地Container存储Nstatic变量中(或在typedef'dstd::integral_constantconstexpr函数中)。另一种选择是编写一个新的(元)函数,其唯一目的是查找N. 我更喜欢第一个选项,但我会在答案中写下第二个选项,因为它的侵入性较小(它不需要对 进行任何更改Container)。

//This can alternatively be written as a trait struct.
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args >
constexpr std::size_t get_N(Container<Type, N, Args...> const&) { return N; }

template <class Container>
auto func(std::size_t i, Container &&container) -> decltype(container[i]) {
    //alternatively, use std::tuple_size or container.size() or whatever
    constexpr std::size_t N = get_N(container);
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return container[i];
}

现在您需要能够container[i]使用 cv-ness 和 value 类别进行转发container。为此,请使用作为std::forward. 这真的很难看,因为标准库中对此没有太多支持(幸运的是,您只需要编写一次,它对于很多不同的问题都很有用)。首先类型计算:

template<typename Prototype, typename T_value, typename T_decayed>
using forward_Const_t = 
    typename std::conditional<
        std::is_const<Prototype>::value || std::is_const<T_value>::value,
        T_decayed const,
        T_decayed
    >::type;

template<typename Prototype, typename T_value, typename T_decayed>
using forward_CV_t = 
    typename std::conditional<
        std::is_volatile<Prototype>::value || std::is_volatile<T_value>::value,
        forward_Const_t<Prototype, T_value, T_decayed> volatile,
        forward_Const_t<Prototype, T_value, T_decayed>
    >::type;

template<typename Prototype, typename T>
struct forward_asT {
    static_assert(
        std::is_reference<Prototype>::value,
        "When forwarding, we only want to be casting, not creating new objects.");
    static_assert(
      !(std::is_lvalue_reference<Prototype>::value &&
        std::is_rvalue_reference<T>::value),
    "Casting an rvalue into an lvalue reference is dangerous");
    typedef typename std::remove_reference<Prototype>::type Prototype_value_t;
    typedef typename std::decay<T>::type T_decayed;
    typedef typename std::remove_reference<T>::type T_value;

    typedef typename std::conditional<
      std::is_lvalue_reference<Prototype>::value,
      forward_CV_t<Prototype_value_t, T_value, T_decayed> &,
      forward_CV_t<Prototype_value_t, T_value, T_decayed> &&>::type type;
};

template<typename Prototype, typename T>
using forward_asT_t = typename forward_asT<Prototype,T>::type;

现在的功能:

//Forwards `val` with the cv qualification and value category of `Prototype` 
template<typename Prototype, typename T>
constexpr auto forward_as(T &&val) -> forward_asT_t<Prototype, T &&> {
    return static_cast<forward_asT_t<Prototype, T &&>>(val);
}

现在已经定义了辅助函数,我们可以简单地写成func

template <typename Container>
auto func(std::size_t i, Container &&container) ->
    decltype(forward_as<Container &&>(container[i]))
{
    constexpr std::size_t N = get_N(container);
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return forward_as<Container &&>(container[i]);
}
于 2014-07-26T17:49:59.713 回答
6

我认为如果不使用通用引用,您将无法利用特殊扣除规则的优势。解决方法有点简单——使用通用引用和特征类来匹配模板和提取N

template<class T> struct matched : std::false_type { };

template< template<class, std::size_t, class...> class Container
    , class Type
    , std::size_t N
    , class... Args > 
struct matched<Container<Type, N, Args...>> : std::true_type {
        constexpr static std::size_t size = N;
};

template
    < class Container, typename=std::enable_if_t<matched<std::decay_t<Container>>::value> >
auto func(std::size_t i, Container&& container) -> decltype(container[0]) { 
    static_assert(matched<std::decay_t<Container>>::size >= 40 && matched<std::decay_t<Container>>::size <= 42, "bla bla bla");
    return container[i];
}

演示

于 2014-07-26T17:09:04.900 回答
3

尝试这样的事情:

template<typename U, typename T> struct F;
template<template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, typename T
, class... Args> struct F<Container<Type, N, Args...>, T> {
    static auto func(std::size_t i, T&& t) {
        static_assert(N >= 40 && N <= 42, "bla bla bla");
        return t[i];
    }
}

template<typename U> auto func(std::size_t i, U&& container) { 
    return F<std::decay<U>::type, U>::func(i, container);
}

不确定这是否值得。

于 2014-07-26T16:54:29.727 回答
1

每当你看到这样的问题时,想想 SFINAE。然后想“不,那是个坏主意,必须有其他方法”。通常这种方式涉及标签调度。

我们可以使用标签调度吗?我们可以

template<class...> struct types{using type=types;};
// in case your C++ library lacks it:
template<class T>using decay_t=typename std::decay<T>::type;
template
< template<class, std::size_t, class...> class Container
, class Type
, std::size_t N
, class... Args
, class Container
>
auto func_internal(
    std::size_t i,
    types<Container<Type, N, Args...>>,
    Container&& container
) -> decltype(container[0]) { 
  static_assert(N >= 40 && N <= 42, "bla bla bla");
  return container[i];
}
template<class Container>
auto func( std::size_t i, Container&& container )
-> func_internal( i, types<decay_t<Container>>{}, std::declval<Container>() )
{
  return func_internal( i, types<decay_t<Container>>{}, std::forward<Container>(container) );
}

我们已经func将类型信息包装到一个types<?>标签中,并将其传递给func_internal它,从标签中提取所有美味的子类型信息types<?>,并从Container&&.

您的主体func只是迁移到func_internal,如果您收到错误并传递了错误的类型,则错误将是types<blah>不匹配types<Container<Type, N, Args...>>,这不是一个糟糕的错误。

您还可以将多个此类匹配捆绑到一个参数中。

于 2014-07-26T18:58:58.760 回答
0

我相信你可以在 C++11 中得到的最接近的东西是这样的:

template<class Container>
struct container_traits{};

template<
  template<class, std::size_t, class...> class Container,
  class Type,
  std::size_t N,
  class... Args>
struct container_traits< Container<Type, N, Args ... > >
{
    typedef Type type;
    enum {size = N};
};

template<class Container,
         unsigned N = container_traits< 
                  typename std::remove_reference<Container>::type >::size>
auto func(std::size_t i, Container && container) -> decltype(container[0]) 
{ 
    static_assert(N >= 40 && N <= 42, "bla bla bla");
    return container[i];
}

例子:

std::array<int,41> a;
func(41,a); // Ok, pased by lvalue ref.

// "static assert, bla bla bla" (would be passed by rvalue-ref)
func(1, std::array<int,2>{}); 

// Error, no suitable overload overload, the only func available fails with SFINAE
func(15, int{}); 
于 2014-07-26T17:54:28.467 回答
0

我只能看到一种解决方案:

template<
template<typename , std::size_t, class...> class ContainerType
, typename Type
, std::size_t N
, class... Args
>
void test(const ContainerType<Type, N, Args...>&){
    static_assert(N >= 40 && N <= 42, "bla bla bla");
}


template
<typename ContainerType> // you need to be less specific in your container type declaration here to allow compiler deduce const ContainerType&& and ContainerType&& for you
auto func(std::size_t i, ContainerType&& container) -> decltype(container[0]) { 
    test(container); // compiler will throw it out because only static check is here.
    return container[i];
}
于 2014-07-26T18:51:46.747 回答