【问题标题】:Short Circuiting Operators in an enable_ifenable_if 中的短路运算符
【发布时间】:2019-02-04 09:17:57
【问题描述】:

我想编写一个模板化函数,它接受array<int, 3>int[3]。我试图在enable_if 中捕捉到这一点:

template<typename T>
enable_if_t<is_array_v<T> && extent_v<T> == 3U || !is_array_v<T> && tuple_size<T>::value == 3U> foo(const T& param) {}

不幸的是,对于int[3]tupple_size 没有定义,这会导致模板在评估短路之前无法编译。

我也尝试过使用conditional 来执行此操作,但同样的问题是在考虑条件之前确保两个选项都对T 有效。

我知道我可以通过专业化来做到这一点。但是代码在函数体中是完全相同的。我讨厌在实现相同的情况下我专注于这一事实。

有没有办法在评估条件之前强制短路?

【问题讨论】:

  • 错别字:extent_vconst T&amp; param。您当然可以使用特化来定义一个 trait 来控制 SFINAE,从而避免重复函数体。
  • @aschepler 对错别字很好。我只是从记忆中输入而不是复制 :( 总是一个糟糕的计划。你的特质评论可能是我正在寻找的。你能详细说明一下吗?
  • bolov 的回答中的my_is_arrayarray_size_or_zero 部分就是我所说的“定义特征”部分。

标签: c++ templates conditional short-circuiting enable-if


【解决方案1】:

我建议另一种方法:2 个重载(总是更喜欢重载而不是模板特化)调用包含公共代码的公共函数:

namespace detail
{
template <class T>
auto foo_impl(const T& a)
{
    // common code
}
}

template <class T>
auto foo(const std::array<T, 3>& a)
{
    detail::foo_impl(a);
}

template <class T>
auto foo(const T(&a)[3])
{
    detail::foo_impl(a);
}

这很清楚,没有麻烦,并且避免了代码重复。

另一种方法是创建自己的特质:

template <class T, std::size_t Size>
struct my_is_array : std::false_type
{};

template <class T, std::size_t Size>
struct my_is_array<std::array<T, Size>, Size> : std::true_type
{};

template <class T, std::size_t Size>
struct my_is_array<T[Size], Size> : std::true_type
{};

template<typename T>
std::enable_if_t<my_is_array<T, 3>::value> foo(const T& param) {}

或者(我其实更喜欢这个):

template <class T>
struct array_size_or_zero : std::integral_constant<std::size_t, 0>
{};

template <class T, std::size_t Size>
struct array_size_or_zero<std::array<T, Size>> : std::integral_constant<std::size_t, Size>
{};

template <class T, std::size_t Size>
struct array_size_or_zero<T[Size]> : std::integral_constant<std::size_t, Size>
{};

template<typename T>
std::enable_if_t<array_size_or_zero<T>::value == 3> foo(const T& param) {}

小心!!:foo 必须有参数引用,否则数组衰减为指针。

【讨论】:

    【解决方案2】:

    简而言之,模板替换必须始终有效。只定义一个特定的模板来匹配数组可能会更容易:

    template <typename T>
    struct IsArrayInt3 { enum: bool { value = false }; };
    
    template <>
    struct IsArrayInt3<int[3]> { enum: bool { value = true }; };
    
    template <>
    struct IsArrayInt3<std::array<int, 3>> { enum: bool { value = true }; };
    

    【讨论】:

    • 继承 std::false_typestd::true_type 更简单,而且这与所有库特征的作用相匹配。
    【解决方案3】:

    利用extent&lt;T&gt; 用于非数组类型的事实为零,因此是假的,disjunction 派生自列表中第一个带短路的真值类型:

    template<typename T>
    enable_if_t<disjunction<extent<T>, tuple_size<T>>::value == 3U> foo(const T& param) {}
    

    这可能太聪明了。注意这里不能使用disjunction_v


    conditional 也应该可以正常工作。诀窍是在选择正确的类型之前不要询问::value

    template<typename T>
    enable_if_t<conditional_t<is_array_v<T>, extent<T>, tuple_size<T>>::value == 3U> 
        foo(const T& param) {}
    

    【讨论】:

    • @aschepler 如果我支持您的评论,我们可以将其视为对 TC 的额外投票吗?
    • 我必须通读the Notes section of disjunction 才能理解这一点。但似乎disjunction 将继续评估后续参数,直到找到不强制转换为 0 的参数。更重要的是,当它找到不强制转换为 0 的参数时,它会停止计算参数。
    • 哇,那么请帮助我理解这里,tuple_size&lt;int[3]&gt; 无效...为什么编译器不关心,除非我使用 _v
    • @JonathanMee 正如答案中所述,这是您要求的代码,但可能不是您应该编写的代码。 “编写你自己的元函数”的答案在最终代码中更容易理解。
    • @JonathanMee 只需命名tuple_size&lt;int[3]&gt; 是完全有效的。无效的是要求其成员value
    猜你喜欢
    • 1970-01-01
    • 2018-06-14
    • 2015-09-26
    • 2012-08-03
    • 1970-01-01
    • 2011-03-05
    • 2023-03-07
    • 2012-02-04
    • 2020-05-09
    相关资源
    最近更新 更多