Need help understanding a template function with complex name parameters

I am studying the Stroustroup book "C ++ Programming 4th Edition". And I'm trying to follow his example of matrix design.

His matrix class is heavily dependent on templates, and I'm trying my best to figure it out. Here is one of the helper classes for this matrix

A Matrix_slice is part of a Matrix implementation that maps a set of indexes to the location of an element. He uses the idea of ​​generalized slices (§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 

Here are the lines that create the subject of my question:

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

earlier in the book, he describes how Enable_if and All () are implemented:

 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...); } 

I have enough information to understand how they work already, and looking at its implementation of Enable_if, I can also output the Convertible function:

 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; } 

So, I can undersand the building blocks of this template function declaration, but I'm confused when I try to understand how they work. I hope you could help

 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 

So what do we get in the end? This design

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

Questions:

  • We do not need the second template argument for Enable_if
  • Why do we have an assignment ('=') for typename
  • What do we get at the end?

Update: You can check the code in the same book that I refer to here C ++ programming language 4th edition on page 841 (Matrix design)

+5
source share
2 answers

This is the main SFINAE. You can read it here , for example.

For answers, I use std::enable_if_t here instead of EnableIf given in the book, but both of them are identical:

  • As mentioned in @GuyGreer's answer, the second template parameter defaults to void .

  • The code can be read as a "normal" definition of a function template

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

    With = the some_unused_type parameter defaults to the type on the right side. And since you do not explicitly use the some_unused_type type explicitly, you also do not need to give it a name and just leave it empty.

    This is a common approach in C ++, also found for function parameters. Check, for example, operator++(int) - no one writes operator++(int i) or anything like that.

  • Everything that happens together - this is SFINAE, which is the abbreviation for changing the substitution - this is not a mistake. There are two cases here:

    • Firstly, if the boolean argument std::enable_if_t is false , you get

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

      Since there is no valid type in rhs typename = , the deduction type fails. However, due to SFINAE, this does not lead to a compile-time error, but rather to removing a function from the overload set.

      The result in practice looks as if the function had not been defined.

    • Secondly, if the boolean argument std::enable_if_t is true , you get

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

      Now typename = void is a valid type definition, so there is no need to remove this function. Thus, it can be used normally.

For your example

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

above means that this function exists only if All(Convertible<Dims,size_t>()... is true . This basically means that the parameters of the function must be integer indices (personally, I would write this in terms of std::is_integral<T> ).

+4
source

Despite the lack of constexpr , std::enable_if is a template that accepts two parameters, and the second default is void . This makes sense when writing a quick alias for this to preserve this convention.

Therefore, the alias must be defined as:

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

I don’t have an understanding of whether this parameter is present in the book by default or not, it just fixes this problem.

The type assignment is called a type alias and does what it says in a gesture when you refer to an alias, really referring to the fact that it is an alias. In this case, this means that when you write Enable_if<b> , the compiler conveniently extends this value to typename std::enable_if<b, void>::type for you, while preserving everything that is needed for additional input.

What you get at the end is a function that can only be called if each parameter passed to it is converted to std::size_t . This allows you to ignore function overloading if special conditions are not met, which is a more powerful method than simply matching types to select a function to call. The link for std::enable_if contains additional information on why you would like to do this, but I warn newbies that this question is getting a little drunk.

+1
source

Source: https://habr.com/ru/post/1240174/


All Articles