【问题标题】:How to use SFINAE to restrict overload to input iterators如何使用 SFINAE 限制输入迭代器的重载
【发布时间】:2014-10-29 09:48:54
【问题描述】:

许多容器类模板除其他外,都有一个以计数和样本元素作为参数的构造函数,以及另一个以一对输入迭代器为参数的构造函数。其他方法如insert 表现出相同的模式。当模板用整数类型实例化时,一个简单的实现会遇到麻烦,并且构造函数(或其他方法)用一对整数调用:它们的类型相等,输入迭代器方法将给出有效的参数类型推导,但是编译失败。

我正在寻找一种优雅的方式来指定输入迭代器方法仅参与重载(等于)实际上是有效输入迭代器类型的参数类型,或者至少不参与整数类型。我的印象是 SFINAE 是要走的路(但很高兴听到不同的说法),但在阅读它时,坦率地说,我不太了解规则,而且示例中提出的解决方案很难称得上优雅.

作为一个附带限制,我希望我的代码与不完整的 C++11 支持的 gcc 4.6 一起使用。值得注意的是,我想避免使用模板别名。这是我笨拙的尝试(裸露的骨头):

#include <iterator>
#include <type_traits>
#include <vector>

template <typename I>
  struct input_iter : public std::integral_constant<bool,
  not std::is_integral<I>::value
  and std::is_base_of
    <std::input_iterator_tag
    ,typename std::iterator_traits<I>::iterator_category
    >::value >
  {};

template<typename T>
  struct container
{
  container (size_t count, const T& init);
  template <typename InputIt,
            typename = typename std::enable_if<input_iter<InputIt>::value>::type >
    container (InputIt first,InputIt last);
};

typedef container<int> int_cont;

void f()
{ std::vector<int> v (5,3);
  int_cont a1 (3u,6); // first constructor
  int_cont a2 (3u,6); // first constructor
  int_cont a3 (3,6); // first constructor
  int_cont a4 (3,6); // first constructor
  int_cont a5 (3,6); // first constructor
  int_cont b(v.begin(),v.end()); // second constructor
}

这是live example。类模板input_iter 试图做一些多余的事情:检查类型不是整数,然后它实际上是一个输入迭代器。最好我只使用第二部分,但这不起作用;我在为I(没有iterator_category)尝试int 时遇到模板实例化错误,显然这不是SFINAE。虽然我不明白为什么会这样,但我添加了第一部分以避免错误,使用 and (&amp;&amp;) 运算符的惰性,但显然无济于事。我实际上可以通过删除条件的第二部分来编译它,所以我真正的问题不是让它以某种方式工作,而是要了解发生了什么。

我注意到一个奇怪的事情是g++ 只给出了一条错误消息,提到了a3 的定义。因此,一方面,使一个参数无符号显然避免尝试迭代器重载(即使另一个参数可以很容易地转换为无符号),另一方面重复 a3 的违规定义 a4a5不会重复错误消息(但如果修复a3 定义,肯定gcc 会拒绝a4 定义)。另请注意,clang++ 并没有指向 a 变量之一的任何特定定义,尽管修复所有变量会使其保持沉默。

我做错了什么,和/或显然应该做不同的事情?

【问题讨论】:

  • 参见stackoverflow.com/q/25290462/981959 -- 特别要注意,在 C++14 之前,使用非迭代器实例化 std::iterator_traits 会导致未定义的行为。
  • 另外,在enable_if 专业化之后,您需要::type。否则它不会禁用任何东西。
  • 查看直接上下文,了解为什么 SFINAE 无法按预期工作。
  • 感谢有关缺少 ::type 的提示。我已经改变了(在问题和链接示例中);不幸的是,我收到的错误消息并没有太大变化。还有@Jarod42,我不明白你所说的直接上下文是什么意思;什么背景?导致错误消息的定义本身看起来完全正常。
  • @MarcvanLeeuwen 他是说 SFINAE 仅在相关声明的直接上下文中起作用。特别是,对typename enable_if&lt;...&gt;::type 的引用是在直接上下文中,因此enable_if&lt;...&gt; 中缺少type typedef 会导致SFINAE,但在input_iter 的定义中对@9​​87654348@ 的引用不在直接上下文中,因此,如果 iterator_categoryiterator_traits&lt;I&gt; 中丢失,则会出现硬错误。

标签: c++ templates c++11


【解决方案1】:

一个相当简单直接的方法是:

// this wont collide
void assign( size_t, const T& );

template<
          typename It,
          typename = decltype(*std::declval<It&>(), ++std::declval<It&>(), void())
        >
void assign(It, It); // SFINAE on *it and ++it

可能不足以提交标准,但
我在生产中使用它,它还没有咬我。

如果您的编译器支持表达式-sfinae,那么笨拙 做同样事情的方法:

template<typename It>
auto assign(It b, It e)
    -> decltype(*b, ++b, void())
{
}

使用尾随返回类型,您不必使用std::declval

【讨论】:

    【解决方案2】:

    在 cmets 的帮助下,以及更多的实验,我将尝试自己制定一个答案。

    我认为替代失败仅是 (SFI)NAE,如果它发生在相关声明的“直接上下文”中,这显然是 14.8.2;8 中使用的一个神圣的(但不是很清楚解释的)术语,其中它说“只有在函数类型及其模板参数类型的直接上下文中的无效类型和表达式才会导致演绎失败”。撇开构造方法实际上没有任何类型不谈,示例中的失败/错误是在尝试为(否则未使用的)第二个模板参数构造默认类型时出现的,但是将派生的整数类型替换为 InputIt 到片段typename std::enable_if...::type。查找 std::enable_if 的布尔(非类型)模板参数的值本身是在“立即上下文”(类型替换)中执行的;上一段 14.8.2;7 对此很清楚(“表达式不仅包括常量表达式,例如出现在数组边界中或作为非类型模板参数出现的表达式……”)。然而,计算该布尔值需要形成类模板特化input_iter&lt;InputIt&gt;,这是一个不再在直接上下文中执行的辅助活动。事实证明,在该模板特化期间,执行了另一个模板特化,即 std::iterator_traits&lt;InputIt&gt;;这也不是在直接的上下文中,但是这个事实在这里是无关紧要的,因为后一种专业化永远不会失败。然而,当InputIt 被推断为整数类型时,结果结构没有适当定义的iterator_category 成员,这会导致特化input_iter&lt;InputIt&gt; 在这种情况下失败。不在原始类型替换的直接上下文中,此失败会使程序格式错误。

    因此,真正的罪魁祸首是将失败与 SFINEA 应用的上下文区分开来的元素,是专业化 input_iter&lt;InputIt&gt;,而 std::iterator_traits&lt;InputIt&gt; 只是间接涉及。因此,可以通过删除input_iter 模板执行的类型推导,并将布尔表达式直接输入enable_if 来编译该示例。由于我想避免使用模板别名,这需要(因为没有布尔值模板之类的东西)完全忘记 input_iter 并将它包含的整个表达式移动到构造函数模板声明中,给出

    template <typename InputIt,
              typename = typename std::enable_if<not std::is_integral<InputIt>::value
    and std::is_base_of
      <std::input_iterator_tag
      ,typename std::iterator_traits<InputIt>::iterator_category
      >::value>::type >
      container (InputIt first,InputIt last);
    

    std::is_integral 部分并不是很有用,可以省略,只留下std::is_base_of 部分。结果代码compiles and runs correctly,实际上即使是gcc 4.6。这表明,尽管外观如此,但一个可以std::iterator_traits 与 SFINAE 一起使用。当模板别名可用时,其中之一可用于将 enable_if&lt;...&gt;::type 部分从构造函数声明中移除,使其看起来更可口,但没有它们我无法看到如何做到这一点并且仍然正确调用 SFINAE。

    【讨论】:

    • 感谢您的解释和回答,对我也有帮助。
    【解决方案3】:

    您不需要为此使用 SFINAE。我会用is_integral 和委托构造函数来做。

    template<typename T>
    struct container
    {
    public:
        container(std::size_t, const T &t);
    private:
        template<typename I>
        // first is integral, delegate to size_t constructor
        container(I first, I last, std::true_type)
          : container{static_cast<std::size_t>(first), last}
        {}
        // first is not integral, assume we have iterators here
        template<typename I>
        container(I first, I last, std::false_type);
    public:
        template<typename I>
        container(I first, I last)
          : container{first, last, std::is_integral<I>{}}
        {}
    };
    

    【讨论】:

    • 我喜欢这个主意。但是有一些说明:(1)用问题中的示例程序,它仍然不能用gcc 4.6编译,大概是因为那个编译器版本不支持委托构造函数;错误消息是error: type ‘container&lt;int&gt;’ is not a direct base of ‘container&lt;int&gt;’ (2) 我尝试使用默认模板参数来执行此操作:template&lt;typename I, typename X = std::is_integral&lt;I&gt; &gt; container (I, I, X); 但后来我找不到正确的语法来定义、离线、单独的专业化,其中@987654325 @ 是 std::true_type 分别。 std::false_type
    • (3) 与我在问题中的尝试相反,它起作用的真正原因是std::is_integral&lt;I&gt; 永远不会失败扩展为 any 类型I 的确定类型,而我的InputIter&lt;I&gt; 确实无法扩展为整数类型I (尽管我努力处理这种情况),据我所知,这是一个硬错误。在我尝试的解决方案中仅使用std::is_integral(否定)确实解决了我的问题(正如我在问题中观察到的那样)。
    猜你喜欢
    • 2021-11-27
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    • 2020-02-18
    相关资源
    最近更新 更多