Нужна помощь, чтобы понять функцию шаблона со сложными параметрами имени типа

Я изучаю книгу Страустроупа "Программирование на С++, 4-е издание". И я пытаюсь следовать его примеру в матричном дизайне.

Его матричный класс сильно зависит от шаблонов, и я изо всех сил стараюсь в них разобраться. Вот один из вспомогательных классов для этой матрицы

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 и 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. Что мы получаем в итоге?

Обновление: код можно посмотреть в той же книге, на которую я ссылаюсь здесь Язык программирования C++, 4-е издание на стр. 841 (Matrix Design)


person Antiusninja    schedule 08.01.2016    source источник
comment
Это не может быть настоящим кодом. Enable_if<All(Convertible<Dims,size_t>()...)> вызовет ошибку компилятора, поскольку аргумент шаблона может быть только выражением, известным во время компиляции. Возвращаемое значение All не является.   -  person SergeyA    schedule 08.01.2016
comment
@SergeyA Я предполагаю, что ему просто не хватает constexpr перед определениями функций. Я почти уверен, что это заставит его работать.   -  person SirGuy    schedule 08.01.2016
comment
@GuyGreer, ОП может. Но чего еще не хватает OP? ;)   -  person SergeyA    schedule 08.01.2016
comment
@SergeyA Ему не хватает параметра шаблона по умолчанию для псевдонима шаблона Enable_If? Это на самом деле не относится к первому комментарию, поэтому я не уверен, чему вы подмигиваете... ;)   -  person SirGuy    schedule 08.01.2016
comment
@GuyGreer, я пытаюсь подчеркнуть, что не могу объяснить, как работает код, если это не так. И я не собираюсь вычитывать вопрос для ОП. Но не стесняйтесь ответить на вопрос, если можете.   -  person SergeyA    schedule 08.01.2016
comment
Вы правы, мне не хватает constexpr в объявлении All(). Я также не могу точно скопировать его в своей визуальной студии 2013, потому что он не поддерживает constexpr. Это тоже проблема, которую я забыл   -  person Antiusninja    schedule 08.01.2016


Ответы (2)


Это основной SFINAE. Вы можете прочитать его, например, здесь.

Для ответов я использую здесь 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 = нет допустимого типа, вывод типа невозможен. Однако из-за 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<Dims,size_t>()... равно true. В основном это означает, что все параметры функции должны быть целыми индексами (лично я бы написал это с точки зрения std::is_integral<T>).

person davidhigh    schedule 08.01.2016
comment
Пара придирок (не стесняйтесь придираться и к моему ответу, если хотите :P), ошибка вывода типа может привести к ошибке компиляции, но этого не произойдет, если в его вместо. - person SirGuy; 08.01.2016
comment
Кроме того, Convertible является более общим, чем std::is_integral, так как последний проверяет вхождение из заранее определенного списка типов, тогда как первый будет принимать пользовательские типы с преобразованием в std::size_t (я почти уверен, что преобразование в любой целочисленный тип будет достаточно, так как после этого выполняется продвижение типа, а не преобразование, чтобы получить std::size_t). - person SirGuy; 08.01.2016
comment
Это большой кусок объяснения. Большое спасибо. - person Antiusninja; 09.01.2016

Несмотря на отсутствующие constexprs, std::enable_if — это шаблон, который принимает два параметра, но второй один по умолчанию void. При написании быстрого псевдонима имеет смысл придерживаться этого соглашения.

Следовательно, псевдоним должен быть определен как:

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

Я понятия не имею, присутствует ли этот параметр по умолчанию в книге или нет, просто это решит эту проблему.

Назначение типа называется псевдонимом типа и делает то, что указано в жесть, когда вы ссылаетесь на псевдоним, вы на самом деле имеете в виду то, что он псевдоним. В данном случае это означает, что когда вы пишете Enable_if<b>, компилятор легко расширяет его до typename std::enable_if<b, void>::type, избавляя вас от лишнего ввода.

В итоге вы получаете функцию, которую можно вызвать только в том случае, если каждый переданный ей параметр можно преобразовать в std::size_t. Это позволяет игнорировать перегрузки функций, если определенные условия не выполняются, что является более мощным методом, чем просто сопоставление типов для выбора функции для вызова. Ссылка на std::enable_if содержит дополнительную информацию о том, почему вы хотели бы это сделать, но я предупреждаю новичков, что эта тема становится довольно опрометчивой.

person SirGuy    schedule 08.01.2016
comment
Спасибо, но все равно не отвечает еще на 2 вопроса в теме - person Antiusninja; 08.01.2016