【问题标题】:Define a struct which is derived from true_type whenever a given SFINAE-able constructor would be taken定义一个从 true_type 派生的结构,无论何时采用给定的 SFINAE 构造函数
【发布时间】:2016-03-06 14:05:41
【问题描述】:

请考虑以下代码sn-p:

template<typename T, class Tuple>
class vector
{
    using size_type = typename Tuple::size_type;

    template<typename... Elements,
        typename = decltype(std::declval<Tuple>().reserve(size_type()))>
        typename = decltype(std::declval<Tuple>().push_back(T())),
    vector(Elements&&... elements)
    { /* ... */ }
};

我想定义一个嵌套结构supports_reserve_push_back,它派生自std::true_type,只要启用上面的构造函数(在另一种情况下,它派生自std::false_type)。

我该怎么做?

【问题讨论】:

  • 如果它是唯一的 c'tor,使用 varargs 方法的旧重载结果可能会这样做。
  • @StoryTeller 不,还有更多的构造函数。特别是,还有另一个具有相同签名的构造函数。
  • 嗯,显而易见的解决方案是复制启用 c'tor 的检查。
  • @StoryTeller 我不知道我需要如何定义结构。你能提供答案吗?

标签: c++ templates c++14 sfinae typetraits


【解决方案1】:

我已将代码修改为build。并根据我的理解实现了您要求的特征。

#include <iostream>
#include <type_traits>
#include <vector>
#include <map> 

namespace example {

template<typename...>
using void_t = void;

template<typename T, class Tuple>
struct vector {
    using size_type = typename Tuple::size_type;
    using tuple_type = Tuple;
    using elem_type = T;

    template<typename... Elements>
    vector(Elements&&... elements)
    { /* ... */ }
};

template <class T, typename = void>
struct supports_reserve_push_back : std::false_type {};

template <class Vec> 
struct supports_reserve_push_back<Vec, void_t<
  decltype(std::declval<typename Vec::tuple_type>().reserve(typename Vec::size_type())),
  decltype(std::declval<typename Vec::tuple_type>().push_back(typename Vec::elem_type())) >
>
  : std::true_type {};

}

int main() {
    std::cout
    << example::supports_reserve_push_back<example::vector<int, std::vector<int>>>::value
    << '\n'
    << example::supports_reserve_push_back<example::vector<int, std::map<int, int>>>::value;
    return 0;
}

注意几点:

  1. 最初编写 c'tor 的方式在否定情况下实例化类时会导致硬错误。这就是我从 c'tor 中删除 chcck 的原因。

  2. 我建议您先定义类型特征,然后使用它们来启用您的 c'tors。

【讨论】:

    【解决方案2】:
    namespace details{
      template<template<class...>class Z,class,class...Ts>
      struct can_apply:std::false_type{};
      template<template<class...>class Z,class...Ts>
      struct can_apply<Z,std::void_t<Z<Ts...>,Ts...>:
      std::true_type{};
    }
    template<template<class...>class Z,class... Ts>
    using can_apply=details::can_apply<Z,void,Ts...>;
    

    将您的 decltypes 包装到模板 usings 中,然后做一些 &amp;&amp; 并完成。

    还有一个和上面类似的std实验。

    template<class T, class U>
    using push_back_r = decltype(std::declval<T>().push_back(std::declval<U>()));
    template<class T>
    using reserve_r = decltype(std::declval<T>().reserve(1));
    
    template<class T, class U>
    constexpr can_apply<push_back_r,T,U> has_push_back={};
    template<class T>
    constexpr can_apply<reserve_r,T> has_reserve={};
    
    template<bool b>using bool_t=std::integral_constant<bool,b>;
    
    template<class T,class U>
    constexpr bool_t<has_push_back<T,U>&&has_reserve<T>>
    efficiently_fillable_with = {};
    

    那么efficiently_fillable_with&lt;T,U&gt; 是真类型,如果你可以用 T 保留空间,然后将 Us 推入其中。 TU 的 r/l 值类别被保留:如果您想了解用右值 Us 填充 T 的非恒定左值:

    efficiently_fillable_with<T&,U>
    

    如果你想用U const&amp;而不是右值填充,请传递U const&amp;

    【讨论】:

    • 我假设用法是template&lt;class T, typename U&gt; using has_push_back = decltype(std::declval&lt;T&amp;&gt;().push_back(std::declval&lt;U&gt;()));template&lt;typename T, class Tuple&gt; constexpr bool tuple_has_reserve_v = type_traits::can_apply&lt;has_push_back, Tuple, T&gt;{};,对吧?如何检查 push_back 方法是否接受右值(而不是左值或 const 引用)?
    • @0xbadf00d 从 usings 中删除 &amp;s,传入 &amp;s 以描述参数的 r/l 值。
    • 请具体说明。我需要在has_push_back 中更改什么?
    • @0xbad 你写的告诉你它是否会接受一个右值。如果你想排除非常量引用通过,做一个测试通过U&amp;,做两个特征X(取U)和Y(取U&),然后做X and not Y,但我怀疑我误解了你的要求似乎很荒谬。你为什么要关心它是否接受右值,但还要关心它拒绝非常量左值? /耸耸肩。
    • 抱歉,我漏掉了一个字:我想检查一下push_back 是否接受右值reference。原因如下:Tuple 可能是std::vector&lt;T&gt;。如果我不在乎,我可以简单地使用template&lt;typename Elements...&gt; vector(Elements&amp;&amp;... elements) : m_elements{ static_cast&lt;T&gt;(std::forward&lt;Elements&gt;(elements))... } {}。这样做的问题是m_elements{ static_cast&lt;T&gt;(std::forward&lt;Elements&gt;(elements))... } 将调用std::vectorstd::initializer_list-构造函数,从而创建elements 的副本而不是移动它们。
    【解决方案3】:
    #include <type_traits>
    #include <utility>
    
    template <typename...>
    using void_t = void;
    
    template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
    struct detect_impl : std::false_type {};
    
    template <template <typename...> class Operation, typename... Args>
    struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
    
    template <template <typename...> class Operation, typename... Args>
    using detect = detect_impl<void_t<>, Operation, Args...>;
    
    template <typename T, typename Sz>
    using has_reserve = decltype(std::declval<T>().reserve(std::declval<Sz>()));
    
    template <typename T, typename U>
    using has_push_back = decltype(std::declval<T>().push_back(std::declval<U>()));
    
    template <typename Tuple, typename T, typename size_type>
    constexpr bool supports_reserve_push_back = detect<has_reserve, Tuple, size_type>{} && detect<has_push_back, Tuple, T>{};
    

    测试:

    template <typename T, class Tuple>
    class vector
    {
    public:
        using size_type = typename Tuple::size_type;
    
        template <typename... Elements, typename U = Tuple,
        std::enable_if_t<supports_reserve_push_back<U&, T, size_type>, int> = 0>
        vector(Elements&&... elements)
        {
        }
    
        template <typename... Elements, typename U = Tuple,
        std::enable_if_t<!supports_reserve_push_back<U&, T, size_type>, int> = 0>
        vector(Elements&&... elements)
        {
        }
    };
    

    DEMO

    【讨论】:

    • has_push_back 中使用std::declval&lt;T&amp;&gt; 而不是std::declval&lt;T&gt; 的原因是什么?为什么你使用std::declval&lt;U&gt;() 而不是U()?假设一个类T 有一个push_back 方法用于某些参数类型U。让我们假设某个类U = foofoo。即使我们将foo 替换为foo&amp;&amp;has_push_back 会评估为真吗?以及如何确保Tpush_back 方法接受右值?
    • @0xbadf00d,使用U() 明确要求类是默认可构造的。这是不合理的,因为许多有用的类不能如此。这就是添加std::declval 的全部原因。
    • @StoryTeller 使用std::declval&lt;T&amp;&gt; 而不是std::declval&lt;T&gt; 的原因是什么?
    • @0xbadf00d 前者产生一个左值
    • @0xbadf00d 你通常说std::vector&lt;int&gt;{}.push_back(1); v.push_back(1); 吗?如果一个成员函数是 ref-qualified,它确实很重要。此外,右值引用可以是左值,这取决于它所使用的上下文,我故意使用单词 lvalue,将左值引用类型声明为其返回类型的函数调用是左值表达式
    【解决方案4】:

    我会尝试以下方法:

    让您的 vector 模板类继承自超类,如下所示:

    template<typename T, class Tuple> class vector
       : public supports_reserve_push_back_impl<
           vector_has_default_constructor<T, Tuple>::value() > {
    
    // ...
    
    }
    

    现在,定义一个采用相同模板参数的vector_has_default_constructor 模板类:

    template<typename T, class Tuple> class vector_has_default_constructor {
    
    public:
    // ...
    
    };
    

    vector_has_default_constructor:

    • 定义一个constexpr bool value() 方法,其签名与向量的构造函数完全相同。这个constexpr 方法返回true。

    • 使用... 签名定义一个重载constexpr bool value(),它在重载决议中的优先级应该较低。这个constexpr 返回 false。

    现在,这种情况被简化为定义两个微不足道的专业化,supports_reserve_push_back_impl&lt;true&gt;supports_reserve_push_back_impl&lt;false&gt;

    supports_reserve_push_back_impl&lt;true&gt; 包含您想要的supports_reserve_push_back 值,并由您的vector 继承。

    supports_reserve_push_back_impl&lt;false&gt; 为空。

    【讨论】:

      猜你喜欢
      • 2021-02-15
      • 2021-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-23
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多