Нужна помощь, чтобы понять функцию шаблона со сложными параметрами 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;
Вопросы:
- Разве нам не нужен второй аргумент шаблона для Enable_if
- Почему у нас есть назначение ('=') для имени типа
- Что мы получаем в итоге?
Обновление: Вы можете проверить код в той же книге, на которую я ссылаюсь здесь. Язык программирования C++, 4-е издание, стр. 841 (Matrix Design)
2 ответа
Это основной SFINAE. Вы можете прочитать это здесь, например.
Для ответов я использую std::enable_if_t
здесь вместо EnableIf
приведены в книге, но оба они идентичны:
Как уже упоминалось в ответе @GuyGreer, по умолчанию для второго параметра шаблона установлено значение
void
,Код можно прочесть как определение шаблона "нормальной" функции.
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)
или что-то типа того.Все, что происходит вместе, это 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
У меня есть больше информации о том, почему вы хотели бы это сделать, но я предупреждаю новичков, что этот предмет становится довольно пьяным.