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

Sho*_*hoe 14 c++ templates c++11 universal-reference

问题

让我们假设一个函数func接受表单中的任何容器Container<Type, N, Args...>(这是一个容器,它将第一个模板参数作为一个类型,第二个std::size_t函数定义容器中有多少个参数)并返回其i第th个元素,当且仅当N它在40和之间时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];
}
Run Code Online (Sandbox Code Playgroud)

然后我需要一个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];
}
Run Code Online (Sandbox Code Playgroud)

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

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];
}
Run Code Online (Sandbox Code Playgroud)

为了定义这个函数的单个版本,make Container<Type, N, Args...>&和两个都有用const Container<Type, N, Args...>&吗?

Man*_*rse 7

如果不实际使用通用引用,就无法获得"通用引用"的优势,因此只需将Container其作为"通用引用"参数即可.如果你这样做,你需要做的就是使用另一种技术来寻找N.

一种选择是简单地使Container存储N在一个static变量(或在typedef"d std::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];
}
Run Code Online (Sandbox Code Playgroud)

现在你需要能够转发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;
Run Code Online (Sandbox Code Playgroud)

现在功能:

//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);
}
Run Code Online (Sandbox Code Playgroud)

既然已经定义了辅助函数,我们可以简单地写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]);
}
Run Code Online (Sandbox Code Playgroud)


T.C*_*.C. 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];
}
Run Code Online (Sandbox Code Playgroud)

演示.