【问题标题】:C++11 enable_if on return type of subscript operator of template parameterC ++ 11 enable_if在模板参数的下标运算符的返回类型上
【发布时间】:2021-06-16 00:49:02
【问题描述】:

我正在使用包含整数或浮点类型的向量类。我想相应地选择一个或另一个函数模板,但推断类型的唯一方法是通过下标运算符 []。

有没有办法在模板参数的[]运算符的返回类型上使用enable_if<is_integral< ...? 比如:

template<class V, typename enable_if<is_integral<V::operator[]>::value, int>::type = 0>
int MyFunc(V vec)
{ return vec[0]; }

template<class V, typename enable_if<!is_integral<V::operator[]>::value, int>::type = 0>
int MyFunc(V vec)
{ return ceil(vec[0]); }

【问题讨论】:

  • decltype(declval&lt;V&gt;()[0])
  • @appleapple,这个解决方案不可靠。例如,它不适用于std::vector&lt;bool&gt;。详情见我的回答。
  • @Enlico 是的,如果您对目标类型一无所知,那么没有什么是真正可靠的,operator[] 甚至可能不接受int
  • @appleapple,这是不准确的。依赖operator[] 意味着您正在对实现细节做出假设。依赖begin 成员函数意味着您假设V 使用通用且众所周知的API。毕竟,如果你将它发送到一个期望它是一种“集合”的函数,它必须提供一种访问元素的方法。因此,假设归结为将该函数命名为 begin 而不是 beginner 或其他任何名称。
  • @Enlico 是的,它总是需要假设,这就是我的观点。

标签: c++ sfinae enable-if


【解决方案1】:

一些一般性建议

我会避免 using namespace std 你的代码暗示(几乎)你使用。

您可以将std::enable_if&lt;T&gt;::typestd::is_integral&lt;T&gt;::value 分别写为std::enable_if_t&lt;T&gt;std::is_integral_v&lt;T&gt; 来缩短它们并使它们更具可读性(例如,搜索“Helper types”here)。

你的问题的答案

您需要将operator[] 应用于某事

你真正想问的是V类型的对象中的项目是否可以通过[]访问,所以你想检查V{}[0]这样的东西是否有效,所以你会写这样的东西(至于为什么需要std::decay_t,见注(¹)):

template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(V{}[0])>>, int> = false>

V 可能不是默认可构造的

如果你想支持V 类型而不假设它们是默认可构造的,你可以使用std::declval 来“假装”你创建了该类型的值(这里需要std::decay_t,原因与上述相同) :

template<class V, typename std::enable_if_t<std::is_integral_v<std::decay_t<decltype(std::declval<V>()[0])>>, int> = false>

operator[] 可以将一些代理返回给V 中的实际实体

一般operator[] 不会返回“概念上”存储在容器中的类型的对象;例如,对于std::vector&lt;bool&gt;operator[] 不返回bool&amp;,而是一个代理类型²;在这些情况下,您真的想依赖存储在容器中的 value_type 类型别名:

template<class V, typename std::enable_if_t<std::is_integral_v<typename V::value_type>, int> = false>

某些东西可能没有value_type-equipped,但只提供.begin()/.end()

作为最后的改进,我们还可以删除V 具有value_type 成员别名的假设(这对于STL 容器是正确的),并且只要求它具有begin 成员函数(STL 容器也有),并利用ranges::iter_value_t³:

template<class V, typename std::enable_if_t<std::is_integral_v<ranges::iter_value_t<decltype(std::declval<V>().begin())>>, int> = 0>

注意

ranges::iter_value_t<decltype(std::declval<V>().begin())>

通常与

不同
std::decay_t<decltype(*std::declval<V>().begin())>

当它们不同时,又是std::vector&lt;bool&gt;


¹std::vector&lt;int&gt;::operator[],例如,返回int&amp;,而不是int,并且int&amp;不是积分:

static_assert(std::is_integral_v<int&>); // fails

² 例如:

// this passes
static_assert(std::is_same_v<int, std::decay_t<decltype(std::declval<std::vector<int>>()[0])>>);
// this doesn't!
static_assert(std::is_same_v<bool, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);
// with my compiler, this one does pass
static_assert(std::is_same_v<std::_Bit_reference, std::decay_t<decltype(std::declval<std::vector<bool>>()[0])>>);

³ ranges::iter_value_t 来自 Range-v3 库的标头 #include &lt;range/v3/iterator/access.hpp&gt;。该库的文档几乎是废话,因此您可以参考cppreference 上的类似 C++20 功能的文档。

【讨论】:

  • 惊人的答案!这是我错过的std::decay_t&lt;decltype(V{}[0]) 部分。哦,std::is_integral_v&lt;T&gt; 只出现在 C++17 中。谢谢!
【解决方案2】:

你为什么不使用std::vector::value_type

template<class V, typename enable_if<is_integral<typename V::value_type>::value, int>::type = 0> 
int MyFunc(V vec)
{ return vec[0]; }

template<class V, typename enable_if<!is_integral<typename V::value_type>::value, int>::type = 0> 
int MyFunc(V vec)
{ return ceil(vec[0]); }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    相关资源
    最近更新 更多