Каков синтаксис и семантика шаблонного кода C ++? - PullRequest
2 голосов
/ 12 марта 2019
template<typename T, size_t M, size_t K, size_t N, typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0>
void fastor2d(){//...}

Я скопировал эту строку кода из cpp-reference (только часть std::enable_if, мне нужно T и все три из size_t), потому что я хотел бы использовать эту функциютолько когда на нем используются плавающие типы ... он не компилируется.

Может ли кто-нибудь объяснить мне, почему и что он вообще делает?Пока я занимаюсь этим, как вы потом вызываете эту функцию?

Каждый урок или вопрос здесь, посвященный SO, подвергается бомбардировке с ответами, и это здорово, но для того, кто не понимает гнезда *** того, чтопроисходит, даже те, которые не очень полезны (извините, если возможно, слегка взволнован или агрессивен)

РЕДАКТИРОВАТЬ: я очень ценю все ответы на данный момент, я понимаю, что моя формулировка, возможно, была немного не в порядке ..Я понимаю, что такое параметр шаблона, и знаю разницу между временем выполнения и временем компиляции и т. д., но я просто не могу понять синтаксис std::enable_if

EDIT2:

template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_integral<T>::value>>
void fastor2d(){
    Fastor::Tensor<T,M,K> A; A.randInt();
}

Это буквально единственное, что мне нужно изменить.Обратите внимание на случайную () часть

template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void fastor2d(){
    Fastor::Tensor<T,M,K> A; A.random();
}

Ответы [ 3 ]

1 голос
/ 12 марта 2019

Прежде всего, я перепишу вашу функцию в рабочей форме

template <typename T, size_t M, size_t K, size_t N,
          std::enable_if_t<std::is_floating_point<T>::value, int> = 0>              
void fastor2d() // ..........................................^^^  int, not T
 { }

Дело в том, что я изменил второй аргумент шаблона std::enable_if_t form T на int.

Я также удалил typename до std::enable_if_t, но это не важно: typename неявно присутствует в _t в конце std::enable_if_t, введенном из C ++14.В C ++ 11 правильная форма:

// C++11 version
   typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0
// ^^^^^^^^            no _t                                     ^^^^^^

Но почему это работает?

Начните с имени: SFINAE.

Это краткая форма для "Ошибка замены"Не является ошибкой ".

Это правило C ++, поэтому, когда вы пишете что-то вроде

 template <int I, std::enable_if_t< I == 3, int> = 0>
 void foo ()
  { }

и I равно 3, условие std::enable_if_t равно true, поэтому std::enable_if_t< I == 3, int> заменяется на int, поэтому foo() включено, но когда I не 3, условие std::enable_if_t, если false, поэтому std::enable_if_t< I == 3, int>, не подставляется, поэтому foo() не включен, но это не ошибка (если из-за перегрузки есть другая включенная функция foo(), которая, очевидно, соответствует вызову).

Так в чем же проблема в вашем коде??

Проблема в том, что std::enable_if_t подставляется, когда первый параметр шаблона равен true, вторым параметром.

Так что если вы пишете

std::enable_if_t<std::is_floating_point<T>::value, T> = 0

и вы называете

fastor2d<float, 0u, 1u, 2u>();

std::is_floating_point<float>::value (но вы также можете использовать более короткую форму std::is_floating_point_v<T> (_v, а не ::value)), чтобы произошла замена, и выget

float = 0

, но, к сожалению, параметр значения шаблона (не типа) не может иметь тип с плавающей запятой, поэтому вы получаете сообщение об ошибке.

Если вместо этого вы используете intиз T, подстановка даст вам

int = 0

, и это правильно.

Другое решение может быть использовано следующая форма

typename = std::enable_if_t<std::is_floating_point<T>::value, T>

, как предложено AndreasLoanjoe, потому что подстановка дает вам

typename = float

, который является допустимым синтаксисом.

Но у этого решения есть недостаток, который не работает, когда вы хотите написать две альтернативные функции, как вВ следующем примере

// the following solution doesn't works

template <typename T, 
          typename = std::enable_if_t<true == std::is_floating_point<T>::value, int>>
void foo ()
 { }

template <typename T, 
          typename = std::enable_if_t<false == std::is_floating_point<T>::value, int>>
void foo ()
 { }

, где работает решение на основе значения

// the following works

template <typename T, 
          std::enable_if_t<true == std::is_floating_point<T>::value, int> = 0>
void foo ()
 { }

template <typename T, 
          std::enable_if_t<false == std::is_floating_point<T>::value, int> = 0>
void foo ()
 { }
1 голос
/ 12 марта 2019

Я постараюсь объяснить это как можно проще, чтобы не вдаваться в подробности языка, поскольку вы просили об этом.

Аргументы шаблона - это аргументы времени компиляции (они не меняются во время выполнения вашего приложения). Аргументы функции выполняются и имеют адрес памяти.

Вызов этой функции будет выглядеть примерно так:

fastor2d<Object, 1, 2, 3>();

В скобках <> вы видите аргументы времени компиляции или, точнее, параметры шаблона, и функция в этом случае принимает 0 аргументов времени выполнения в скобках (). Последний аргумент времени компиляции имеет аргумент по умолчанию, который используется для проверки того, должна ли функция компилироваться вообще (тип enable_if). Если вы хотите более четко узнать, что включить, следует ли искать термин SFINAE, который представляет собой метод шаблонного метапрограммирования, используемый для определения, должна ли функция или класс существовать или нет.

Вот короткий пример SFINAE:

template<typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void function(T arg)
{
}

function(0.3f);    //OK
function(0.0);     //OK double results in std::is_floating_point<double>::value == true
function("Hello"); //Does not exist (T is not floating point)

Причина, по которой третий вызов функции завершается неудачей, заключается в том, что функция не существует. Это связано с тем, что если enable, функция не существует, когда bool времени компиляции, переданный в качестве аргумента шаблона, имеет значение false.

std::is_floating_point<std::string>::value == false

Обратите внимание, что многие люди согласны с тем, что синтаксис SFINAE ужасен и что большая часть кода SFINAE больше не понадобится с введением концепций и ограничений в C ++ 20.

0 голосов
/ 12 марта 2019

Вместо нисходящего подхода, начинающегося с вашего фрагмента кода, я возьму восходящий подход, чтобы объяснить некоторые важные детали о шаблонах и используемых инструментах и ​​методах.

В сущности, шаблоны - это инструмент, который позволяет вам писать код на C ++, который применяется к диапазону возможных типов, не строго для фиксированного типа.В языке со статической типизацией это, во-первых, отличный инструмент для повторного использования кода без ущерба для безопасности типов, но, в частности, в C ++ шаблоны очень мощные, поскольку они могут быть специализированными .

КаждымиОбъявление шаблона начинается с ключевого слова template и списка типа или не тип (то есть значение ) параметров.Параметры типа используют специальное ключевое слово typename или class и используются, чтобы позволить вашему коду работать с различными типами.Нетипичные параметры просто используют имя существующего типа, и они позволяют применять ваш код к диапазону значений , которые известны во время компиляции.

Очень простая шаблонная функцияможет выглядеть следующим образом:

template<typename T> // declare a template accepting a single type T
void print(T t){ // print accepts a T and returns void
    std::cout << t; // we can't know what this means until the point where T is known
}

Это позволяет нам безопасно повторно использовать код для диапазона возможных типов, и мы можем использовать его следующим образом:

int i = 3;
double d = 3.14159;
std::string s = "Hello, world!";
print<int>(i);
print<double>(d);
print<std::string>(s);

Компилятор даже умныйДостаточно, чтобы вывести параметр шаблона T для каждого из них, так что вы можете спокойно использовать следующий, функционально идентичный код:

print(i);
print(d);
print(s);

Но предположим, что вы хотите, чтобы print вел себя по-разному для одного типа,Предположим, например, что у вас есть пользовательский класс Point2D, который требует специальной обработки.Вы можете сделать это с помощью шаблона специализации :

template<> // this begins a (full) template specialization
void print<Point2D>(Point2D p){ // we are specializing the existing template print with T=Point2D
    std::cout << '(' << p.x << ',' << p.y << ')';
}

Теперь, когда мы используем print с T=Point2D, выбирается специализация.Это действительно полезно, например, если универсальный шаблон просто не имеет смысла для одного конкретного типа.

std::string s = "hello";
Point2D p {0.5, 2.7};
print(s); // > hello
print(p); // > (0.5,2.7)

Но что, если мы хотим специализировать шаблон для множества типов одновременно, основываясь на простом условии?Это где вещи становятся немного мета.Во-первых, давайте попробуем выразить условие так, чтобы их можно было использовать внутри шаблонов.Это может быть немного сложно, потому что нам нужны ответы во время компиляции.

Условие здесь будет состоять в том, что T - это число с плавающей запятой, которое истинно, если T=float или T=double, и ложно в противном случае.Это на самом деле довольно просто сделать с помощью одной только специализации шаблона.

// the default implementation of is_floating_point<T> has a static member that is always false
template<typename T>
struct is_floating_point {
    static constexpr bool value = false;
};

// the specialization is_floating_point<float> has a static member that is always true
template<>
struct is_floating_point<float> {
    static constexpr bool value = true;
};

// the specialization is_floating_point<double> has a static member that is always true
template<>
struct is_floating_point<double> {
    static constexpr bool value = true;
}

Теперь мы можем запросить любой тип, чтобы узнать, является ли это числом с плавающей запятой:

is_floating_point<std::string>::value == false;
is_floating_point<int>::value == false;
is_floating_point<float>::value == true;
is_floating_point<double>::value == true;

Но как мы можемиспользовать это условие времени компиляции внутри другого шаблона?Как мы можем сказать компилятору, какой шаблон выбрать при наличии множества возможных специализаций шаблонов?

Это достигается за счет использования правила C ++ под названием SFINAE , которое в базовом английском языкеговорит: «когда существует много возможных шаблонов, а текущий не имеет смысла *, просто пропустите его и попробуйте следующий».

* При попытке заменить шаблон есть список ошибокаргументы в шаблонном коде, которые вызывают игнорирование шаблона без немедленной ошибки компилятора .Список немного длинный и сложный .

Один из возможных способов, по которым шаблон не имеет смысла, - это попытка использовать тип, который не существует.

template<T>
void foo(T::nested_type x); // SFINAE error if T does not contain nested_type

Это тот же трюк, который std::enable_if использует под капотом.enable_if - это шаблонный класс, принимающий условия типа T и bool, и он содержит вложенный тип type, равный T , только когда условие истинно .Этого также довольно легко достичь:

template<bool condition, typename T>
struct enable_if {
    // no nested type!
};

template<typename T> // partial specialization for condition=true but any T
struct enable_if<true, T> {
    typedef T type; // only exists when condition=true
};

Теперь у нас есть помощник, который мы можем использовать вместо любого типа.Если условие, которое мы передаем, является истинным, то мы можем безопасно использовать вложенный тип.Если условие, которое мы передаем, ложно, то шаблон больше не рассматривается.

template<typename T>
std::enable_if<std::is_floating_point<T>::value, void>::type // This is the return type!
numberFunction(T t){
    std::cout << "T is a floating point";
}

template<typename T>
std::enable_if<!std::is_floating_point<T>::value, void>::type
numberFunction(T t){
    std::cout << "T is not a floating point";
}

Я полностью согласен с тем, что std::enable_if<std::is_floating_point<T>::value, void>::type - грязный способ расшифровки типа.Вы можете прочитать это как "void, если T - с плавающей точкой, и бессмысленная чепуха в противном случае."

Наконец, разберем ваш пример:

// we are declaring a template
template<
    typename T, // that accepts some type T,
    size_t M,   // a size_t M,
    size_t K,   // a size_t K,
    size_t N,   // a size_t N,
    // and an unnamed non-type that only makes sense when T is a floating point
    typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0
>
void fastor2d(){//...}

Обратите внимание на = 0 в конце.Это просто значение по умолчанию для окончательного параметра шаблона, и оно позволяет вам указать значения T, M, K и N, но не пятый параметр.Используемое здесь enable_if означает, что вы можете предоставить другие шаблоны, называемые fastor2d, с их собственными наборами условий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...