Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need help to understand template function with complex typename parameters

I'm examining a Stroustroup's book "C++ Programming 4th edition". And I'm trying to follow his example on matrix design.

His matrix class heavily depends on templates and I try my best to figure them out. Here is one of the helper classes for this matrix

A Matrix_slice is the part of the Matrix implementation that maps a set of subscripts to the location of an element. It 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 build up 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 by looking at his Enable_if implementation I can deduce Convertible function as well:

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 trying to understand how they work altogather. I hope that 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 construct

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

The questions are:

  1. Don't we need the second template argument for Enable_if
  2. Why do we have assignment ('=') for a typename
  3. What do we get in the end?

Update: You can check the code in the same book that I'm referencing here The C++ Programming Language 4th edition at page 841 (Matrix Design)

like image 567
Antiusninja Avatar asked Jan 08 '16 13:01

Antiusninja


2 Answers

This is basic SFINAE. You can read it up here, for example.

For the answers, I'm using std::enable_if_t here instead of the EnableIf given in the book, but the two are identical:

  1. As mentioned in the answer by @GuyGreer, the second template parameter of is defaulted to void.

  2. The code can be read as a "normal" function template definition

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

    With the =, the parameter some_unused_type is defaulted to the type on the right-hand side. And as one does not use the type some_unused_type explicitly, one also does not need to give it a name and simply leave it empty.

    This is the usual approach in C++ also found for function parameters. Check for example operator++(int) -- one does not write operator++(int i) or something like that.

  3. What's happening all together is SFINAE, which is an abbreviation for Substitution Failure Is Not An Error. There are two cases here:

    • First, if the boolean argument of std::enable_if_t is false, one gets

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

      As there is no valid type on the rhs of typename =, type deduction fails. Due to SFINAE, however, it does not lead to a compile-time error but rather to a removal of the function from the overload set.

      The result in practice is as if the function would have not been defined.

    • Second, if the boolean argument of std::enable_if_t is true, one gets

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

      Now typename = void is a valid type definition and so there is no need to remove the function. It can thus be normally used.

Applied to your example,

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

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

like image 162
davidhigh Avatar answered Sep 22 '22 18:09

davidhigh


The missing constexprs notwithstanding, std::enable_if is a template that takes two parameters, but the second one is defaulted to void. It makes sense when writing up a quick alias to this to keep that convention.

Hence the alias should be defined as:

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

I have no insight into whether this default parameter is present in the book or not, just that this will fix that issue.

The assignment of a type is called a type alias and does what it says on the tin, when you refer to the alias, you're actually referring to what it aliases. In this case it means that when you write Enable_if<b> the compiler handily expands that to typename std::enable_if<b, void>::type for you, saving you all that extra typing.

What you get in the end is a function that is only callable if every parameter you passed to it is convertible to a std::size_t. This allows overloads of functions to be ignored if specific conditions are not met which is more a powerful technique than just matching types up for selecting what function to call. The link for std::enable_if has more information on why you would want to do that, but I warn beginners that this subject gets kinda heady.

like image 21
SirGuy Avatar answered Sep 19 '22 18:09

SirGuy