需要帮助来理解具有复杂typename参数的模板函数

时间:2016-01-08 13:52:59

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

我正在研究Stroustroup的书“C ++编程第4版”。而我正试图在矩阵设计上遵循他的例子。

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

  

Matrix_slice是映射a的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和All():

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. 为什么我们要为typename
  4. 分配('=')
  5. 我们到底得到了什么?
  6. 更新 您可以查看我在此处引用的同一本书中的代码 The C++ Programming Language 4th edition第841页(矩阵设计)

2 个答案:

答案 0 :(得分:4)

这是基本的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,它是替换失败不是错误的缩写。这里有两种情况:

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

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

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

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

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

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

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

  4. 应用于您的示例,

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

    以上意味着仅当All(Convertible<Dims,size_t>()...true时此函数才存在。这基本上意味着函数参数应该都是整数索引(我个人而言,我会根据std::is_integral<T>写出来。)

答案 1 :(得分:1)

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

因此,别名应定义为:

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

我不知道书中是否存在此默认参数,只是这样可以解决该问题。

一个类型的赋值被称为type alias,并且它在锡上执行的操作,当您引用别名时,您实际上指的是它的别名。在这种情况下,这意味着当您编写Enable_if<b>时,编译器会轻松地将其扩展为typename std::enable_if<b, void>::type,为您节省所有额外输入。

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