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

Я изучаю книгу Строуструпа "Программирование на C++, 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)

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> тем не мение).

Пропажа constexpr несмотря на это, 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 У меня есть больше информации о том, почему вы хотели бы это сделать, но я предупреждаю новичков, что этот предмет становится довольно пьяным.

Другие вопросы по тегам