【问题标题】:Need help to understand template function with complex typename parameters需要帮助来理解具有复杂类型名参数的模板函数
【发布时间】:2016-01-08 13:52:59
【问题描述】:

我正在研究 Stroustroup 的书“C++ Programming 4th edition”。我正在尝试效仿他的矩阵设计示例。

他的矩阵类在很大程度上依赖于模板,我尽力弄清楚它们。 这是此矩阵的辅助类之一

Matrix_slice 是 Matrix 实现的一部分,它映射一个 元素位置的一组下标。它使用了这个想法 广义切片(§40.5.6):

template<size_t N>
struct Matrix_slice {
    Matrix_slice() = default; // an empty matrix: no elements
    Matrix_slice(size_t s, initializer_list<size_t> exts); // extents
    Matrix_slice(size_t s, initializer_list<size_t> exts, initializer_list<siz e_t> strs);// extents and strides
    template<typename... Dims> // N extents
    Matrix_slice(Dims... dims);


    template<typename... Dims,
    typename = Enable_if<All(Convertible<Dims,size_t>()...)>>
    size_t operator()(Dims... dims) const; // calculate index from a set of    subscripts

    size_t size; // total number of elements
    size_t start; // star ting offset
    array<size_t,N> extents; // number of elements in each dimension
    array<size_t,N> strides; // offsets between elements in each dimension
};
I

以下是构成我的问题主题的几行:

template<typename... Dims,
        typename = Enable_if<All(Convertible<Dims,size_t>()...)>>
        size_t operator()(Dims... dims) const; // calculate index from a set of    subscripts

在本书的前面,他描述了 Enable_if 和 A​​ll() 是如何实现的:

template<bool B,typename T>
using Enable_if = typename std::enable_if<B, T>::type;


constexpr bool All(){
    return true;
}
template<typename...Args>
constexpr bool All(bool b, Args... args)
{
    return b && All(args...);
}

我已经有足够的信息来了解它们是如何工作的,并且通过查看他的 Enable_if 实现,我也可以推断出 Convertible 函数:

template<typename From,typename To>
bool Convertible(){
    //I think that it looks like that, but I haven't found 
    //this one in the book, so I might be wrong
    return std::is_convertible<From, To>::value;
}

所以,我可以理解这个模板函数声明的构建块 但是当我试图理解它们是如何工作的时候我很困惑。 希望能帮到你

template<typename... Dims,
//so here we accept the fact that we can have multiple arguments like (1,2,3,4)

        typename = Enable_if<All(Convertible<Dims,size_t>()...)>>
        //Evaluating and expanding from inside out my guess will be
        //for example if Dims = 1,2,3,4,5
        //Convertible<Dims,size_t>()... = Convertible<1,2,3,4,5,size_t>() =
        //= Convertible<typeof(1),size_t>(),Convertible<typeof(2),size_t>(),Convertible<typeof(3),size_t>(),...
        //= true,true,true,true,true

        //All() is thus expanded to All(true,true,true,true,true)            
        //=true;

        //Enable_if<true>
        //here is point of confusion. Enable_if takes two tamplate arguments, 
        //Enable_if<bool B,typename T>
        //but here it only takes bool

        //typename = Enable_if(...) this one is also confusing

        size_t operator()(Dims... dims) const; // calculate index from a set of    subscripts

那么我们最终得到了什么? 这个结构

template<typename ...Dims,typename = Enable_if<true>>
size_t operator()(Dims... dims) const;

问题是:

  1. 我们不需要 Enable_if 的第二个模板参数吗
  2. 为什么我们要为类型名赋值 ('=')
  3. 我们最终会得到什么?

更新: 您可以在我在这里引用的同一本书中查看代码 The C++ Programming Language 4th edition 第 841 页(矩阵设计)

【问题讨论】:

  • 这不是真正的代码。 Enable_if&lt;All(Convertible&lt;Dims,size_t&gt;()...)&gt; 将触发编译器错误,因为模板参数只能是编译时已知的表达式。 All 的返回值不是。
  • @SergeyA 我认为他只是在函数定义前面缺少constexpr。我很确定这会成功。
  • @GuyGreer,OP 可能。但除了 OP 还缺少什么? ;)
  • @SergeyA 他缺少模板别名Enable_If 的默认模板参数?不过,这与第一条评论并没有真正的关系,所以我不确定你在眨眼什么……;)
  • @GuyGreer,我试图指出我无法解释代码在不工作时是如何工作的。我不会为 OP 校对这个问题。但如果可以,请随时回答这个问题。

标签: c++ metaprogramming variadic-templates variadic-functions enable-if


【解决方案1】:

这是基本的 SFINAE。例如,您可以阅读here

对于答案,我在这里使用std::enable_if_t,而不是书中给出的EnableIf,但两者是相同的:

  1. @GuyGreer 的回答中提到,第二个模板参数默认为void

  2. 代码可以作为“正常”的函数模板定义来阅读

    template<typename ...Dims, typename some_unused_type = enable_if_t<true> >
    size_t operator()(Dims... dims) const;
    

    对于=,参数some_unused_type 默认为右侧的类型。而且由于没有明确使用some_unused_type 类型,因此也不需要给它一个名称,只需将其留空即可。

    这是 C++ 中常用的方法,也适用于函数参数。检查例如operator++(int) - 不写operator++(int i) 或类似的东西。

  3. 一起发生的是 SFINAE,它是 Substitution Failure Is Not An Error 的缩写。这里有两种情况:

    • 首先,如果std::enable_if_t 的布尔参数是false,则得到

      template<typename ...Dims, typename = /* not a type */>
      size_t operator()(Dims ... dims) const;
      

      由于typename =的右轴没有有效类型,类型推导失败。但是,由于 SFINAE,它不会导致编译时错误,而是会从重载集中删除函数。

      实践中的结果就像函数没有被定义一样。

    • 其次,如果std::enable_if_t的布尔参数是true,则得到

      template<typename ...Dims, typename = void>
      size_t operator()(Dims... dims) const;
      

      现在typename = void 是一个有效的类型定义,因此不需要删除该函数。这样就可以正常使用了。

应用于您的示例,

template<typename... Dims,
        typename = Enable_if<All(Convertible<Dims,size_t>()...)>>
        size_t operator()(Dims... dims) const;

上面的意思是这个函数只有在All(Convertible&lt;Dims,size_t&gt;()...true时才存在。这基本上意味着函数参数都应该是整数索引(我个人,我会写成std::is_integral&lt;T&gt;)。

【讨论】:

  • 一些吹毛求疵(如果你喜欢,也可以随意挑剔我的答案:P),类型推导失败可以导致编译错误,它只是不会如果可以找到另一个函数重载来代替它。
  • 另外,Convertiblestd::is_integral 更通用,因为后者检查预先确定的类型列表中的出现,而前者将接受用户定义的类型并转换为 @ 987654346@(我很确定转换为任何整数类型就足够了,因为之后会进行类型提升而不是转换以获得std::size_t)。
  • 这是一个很大的解释。非常感谢。
【解决方案2】:

尽管缺少constexprs,std::enable_if 是一个带有两个参数的模板,但第二个参数默认为void。在为此编写一个快速别名以保持该约定时,这是有道理的。

因此别名应定义为:

   template <bool b, class T = void>
   using Enable_if = typename std::enable_if<b, T>::type;

我不知道书中是否存在这个默认参数,只是这会解决这个问题。

类型的赋值称为type alias,它按照它在锡上所说的那样做,当你引用别名时,你实际上是在引用它的别名。在这种情况下,这意味着当您编写 Enable_if&lt;b&gt; 时,编译器会为您轻松地将其扩展为 typename std::enable_if&lt;b, void&gt;::type,从而为您节省所有额外的输入。

你最终得到的是一个函数,它只有在你传递给它的每个参数都可以转换为std::size_t 时才可调用。如果不满足特定条件,这允许忽略函数的重载,这是一种比仅匹配类型以选择要调用的函数更强大的技术。 std::enable_if 的链接提供了有关您为什么要这样做的更多信息,但我警告初学者,这个主题有点令人兴奋。

【讨论】:

  • 谢谢,但仍然没有回答该主题中的另外 2 个问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多