在没有通用参考的情况下获得通用参考的优势

时间:2014-07-26 16:32:20

标签: c++ templates c++11 universal-reference

问题

让我们假设一个函数func接受Container<Type, N, Args...>形式的任何容器(这是一个容器,它将第一个模板参数作为一个类型,第二个std::size_t定义了多少个参数当且仅当i介于N40之间时,才会在容器中返回其42元素。

此类容器的一个示例是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...>&

6 个答案:

答案 0 :(得分:7)

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

一种选择是在Container变量(或N'd statictypedef中简单地std::integral_constant存储constexpr功能)。另一种选择是编写一个新的(元)函数,其唯一目的是找到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和值类别转发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]);
}

答案 1 :(得分: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];
}

Demo

答案 2 :(得分: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);
}

不确定是否值得。

答案 3 :(得分:1)

每当你看到这样的问题时,请考虑SFINAE。然后思考&#34;不,这是一个坏主意,必须有另一种方式&#34;。通常这种方式涉及标签调度。

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

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...>>不匹配,这不是错误。

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

答案 4 :(得分: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{}); 

答案 5 :(得分: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];
}