简体   繁体   English

C++ 模板化代码的语法和语义是什么?

[英]What are the syntax and semantics of C++ templated code?

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(){//...}

I copied this line of code from cpp-reference(only the std::enable_if part, i do need T and all three of the size_t 's), because i would like to use this function only when floating_types are used on it ... it does not compile.我从 cpp-reference 复制了这行代码(只有std::enable_if部分,我确实需要T和所有三个size_t ),因为我只想在使用size_t时使用这个函数。 . 它不编译。

Could somebody explain to me, why, and what it even does?有人可以向我解释,为什么,它甚至做什么? While i am at it, how do you call this function afterwards?当我在做的时候,你之后如何调用这个函数?

Every tutorial or question here on SO gets bombed with answers, and that is great, but to someone who does not understand jacks*** of what is happening, even those are not really helpful.(sry, if possibly slightly agitated or aggressive) SO上的每个教程或问题都会被答案轰炸,这很好,但是对于不了解正在发生的事情的人来说,即使是那些也没有真正的帮助。(对不起,如果可能有点激动或咄咄逼人)

EDIT: i greatly appreciate all answers as of now, i realize that my wording might have been a bit off ... i understand what a template parameter is, and know the difference between runtime and compiletime etc, but i just cant get a good grasp of the syntax behind std::enable_if编辑:我非常感谢到目前为止的所有答案,我意识到我的措辞可能有点不对......我理解模板参数是什么,并且知道运行时和编译时等之间的区别,但我就是无法得到一个好的掌握std::enable_if背后的语法

EDIT2:编辑2:

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();
}

This is literally the only thing i need changed.这实际上是我唯一需要改变的东西。 Notice the random() part注意 random() 部分

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();
}

I'll try to explain this as simple as possible not to go into the language details too much since you asked for it.我将尽可能简单地解释这一点,因为您要求它不要过多地介绍语言细节。

Template arguments are compile time arguments (they do not change during the run-time of your application).模板参数是编译时参数(它们在应用程序运行时不会改变)。 Function arguments are run-time and have a memory address.函数参数是运行时的并且有一个内存地址。

Calling this function would look something like this:调用这个函数看起来像这样:

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

In the <> brackets you see the compile-time arguments or more accurately the template parameters, and the function in this case takes 0 runtime arguments in the () brackets.在 <> 括号中,您可以看到编译时参数或更准确地说是模板参数,本例中的函数在 () 括号中采用 0 个运行时参数。 The last compile time argument has a default argument which is used to check whether the function should compile at all (enable_if type).最后一个编译时参数有一个默认参数,用于检查函数是否应该编译(enable_if 类型)。 If you want to know more clearly what enable if does you should search for the term SFINAE, which is a template metaprogramming technique used to determine whether a function or class should exist or not.如果您想更清楚地知道什么使能,您应该搜索术语 SFINAE,这是一种模板元编程技术,用于确定函数或类是否应该存在。

Here is a short SFINAE example:这是一个简短的 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)

The reason the third function call fails, is because the function does not exist.第三个函数调用失败的原因是该函数不存在。 This is because the enable if caused the function not to exist when the compile-time bool that is passed in as its' template argument is false.这是因为当作为其模板参数传入的编译时 bool 为 false 时,启用 if 导致该函数不存在。

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

Do note that a lot of people agree that the SFINAE syntax is horrible and that a lot of SFINAE code will not be necessary anymore with the introduction of concepts and constraints in C++ 20.请注意,很多人都同意 SFINAE 语法很糟糕,并且随着 C++ 20 中概念和约束的引入,不再需要大量 SFINAE 代码。

Rather than a top-down approach starting with you code snippet, I'll take a bottom-up approach to explain some important details about templates and what tools and techniques are involved.我将采用自下而上的方法来解释有关模板的一些重要细节以及所涉及的工具和技术,而不是从您的代码片段开始的自上而下的方法。


At heart, templates are a tool that let you write C++ code that applies to a range of possible types, not strictly for a fixed type.从本质上讲,模板是一种工具,可让您编写适用于一系列可能类型的 C++ 代码,而不是严格适用于固定类型。 In a statically-typed language, this is firstly a great tool for reusing code without sacrificing type safety, but in C++ in particular, templates are very powerful because they can be specialized .在静态类型语言中,这首先是一个很好的工具,可以在不牺牲类型安全的情况下重用代码,但特别是在 C++ 中,模板非常强大,因为它们可以专门化

Every template declaration begins with the keyword template , and a list of type or non-type (ie value ) parameters.每个模板声明都以关键字template和一个类型或非类型(即)参数列表开始。 Type parameters use the special keyword typename or class , and are used to let your code work over a range of types.类型参数使用特殊关键字typenameclass ,用于让您的代码在一系列类型上工作。 Non-type parameters simply use the name of an existing type, and these let you apply your code to a range of values that are known at compile-time.非类型参数仅使用现有类型的名称,这些参数让您可以将代码应用于编译时已知的一系列

A very basic templated function might look like the following:一个非常基本的模板化函数可能如下所示:

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
}

This lets us reuse code safely for a range of possible types, and we can use it as follows:这让我们可以安全地为一系列可能的类型重用代码,我们可以按如下方式使用它:

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

The compiler is even smart enough to deduce the template parameter T for each of these, so you can safely get away with the following, functionally identical code:编译器甚至足够聪明,可以为其中的每一个推导出模板参数T ,因此您可以安全地使用以下功能相同的代码:

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

But suppose you want print to behave differently for one type.但是假设您希望print对一种类型有不同的表现。 Suppose, for example, you have a custom Point2D class that needs special handling.例如,假设您有一个需要特殊处理的自定义Point2D类。 You can do this with a template specialization :您可以使用模板专业化来做到这一点:

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 << ')';
}

Now, anytime we use print with T=Point2D , the specialization is chosen.现在,无论何时我们将printT=Point2D ,都会选择专业化。 This is really useful, for example, if the generic template just doesn't make sense for one specific type.这非常有用,例如,如果通用模板对一种特定类型没有意义。

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

But what if we want to specialize a template for many types at once, based on a simple condition?但是,如果我们想根据一个简单的条件一次为多种类型专门化一个模板呢? This is where things become a little meta.这就是事情变得有点元的地方。 First, let's try to express a condition in a way that lets them be used inside templates.首先,让我们尝试以一种允许它们在模板中使用的方式来表达条件。 This can be a little tricky because we need compile-time answers.这可能有点棘手,因为我们需要编译时的答案。

The condition here will be that T is a floating point number, which is true if T=float or T=double and false otherwise.这里的条件是T是一个浮点数,如果T=floatT=double则为真,否则为假。 This is actually fairly simple to achieve with template specialization alone.单独使用模板专业化来实现这一点实际上相当简单。

// 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;
}

Now, we can query any type to see if it's a floating point number:现在,我们可以查询任何类型以查看它是否是浮点数:

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;

But how can we use this compile-time condition inside another template?但是我们如何在另一个模板中使用这个编译时条件呢? How can we tell the compiler which template to choose when there are many possible template specializations to choose from?当有许多可能的模板特化可供选择时,我们如何告诉编译器选择哪个模板?

This is achieved by taking advantage of a C++ rule called SFINAE , which in basic English, says, "when there are many possible template specializations to choose from, and the current one doesn't make sense*, just skip it and try the next one."这是通过利用名为SFINAE的 C++ 规则来实现的,该规则在基本英语中说,“当有许多可能的模板专业可供选择,而当前的没有意义时*,只需跳过它并尝试下一个一。”

  • There's a list of errors, when attempting to substitute template arguments into templated code, that cause the template to be ignored without an immediate compiler error .有一个错误列表,当尝试将模板参数替换为模板化代码时,会导致模板被忽略而不会立即出现编译器错误 The list is a bit long and complex .该列表有点长且复杂

One possible way that a template doesn't make sense is if it tries to use a type that doesn't exist.模板没有意义的一种可能方式是它尝试使用不存在的类型。

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

This is the exact same trick that std::enable_if uses under the hood.这与std::enable_ifstd::enable_if使用的技巧完全相同。 enable_if is a template class accepting a type T and a bool condition, and it contains a nested type type equal to T only when the condition is true . enable_if是一个接受类型Tbool条件的模板类,它包含一个嵌套类型type仅当条件为 true 时才等于T This is also pretty easy to achieve:这也很容易实现:

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
};

Now we have a helper that we can use in place of any type.现在我们有了一个可以代替任何类型的助手。 If the condition we pass is true, then we can safely use the nested type.如果我们传递的条件为真,那么我们可以安全地使用嵌套类型。 If the condition we pass is false, then the template is no longer considered.如果我们传递的条件为假,则不再考虑模板。

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

I completely agree that std::enable_if<std::is_floating_point<T>::value, void>::type is a messy way to spell out a type.我完全同意std::enable_if<std::is_floating_point<T>::value, void>::type是一种拼写std::enable_if<std::is_floating_point<T>::value, void>::type的混乱方式。 You can read it as " void if T is floating point, and otherwise stop and try the next overload"您可以将其读作“如果 T 是浮点数则为void ,否则停止并尝试下一个重载”


Finally, to take apart your example:最后,拆开你的例子:

// 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(){//...}

Note the = 0 at the end.注意最后的= 0 That's simply a default value for the final template parameter, and it lets you get away with specifying T , M , K , and N but not the fifth parameter.这只是最后一个模板参数的默认值,它让您无需指定TMKN而不是第五个参数。 The enable_if used here means that you can provide other templates called fastor2d , with their own sets of conditions.这里使用的enable_if意味着您可以提供其他名为fastor2d模板,以及它们自己的条件集。

First of all, I'll rewrite your function in a working form首先,我将以工作形式重写您的函数

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
 { }

The point is that I've changed the second template argument of std::enable_if_t form T to int .关键是我已将std::enable_if_t表单T的第二个模板参数更改为int

I've also removed the typename before std::enable_if_t but isn't important: the typename is implicit in the _t at the end of std::enable_if_t , introduced from C++14.我还删除了typenamestd::enable_if_t但并不重要:该typename是隐式_t在年底std::enable_if_t ,从C ++ 14引入。 In C++11 the correct form is在 C++11 中,正确的形式是

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

But why it works?但它为什么有效?

Start from the name: SFINAE.从名字开始:SFINAE。

Is a short form for "Substitution Failure Is Not An Error".是“替换失败不是错误”的简写形式。

It's a C++ rule so that when you write some thing as这是一个 C++ 规则,所以当你写一些东西时

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

and I is 3 , the condition of std::enable_if_t is true so std::enable_if_t< I == 3, int> is substituted with int so foo() is enabled but when I isn't 3 , the condition of std::enable_if_t if false so std::enable_if_t< I == 3, int> is not substituted so foo() isn't enabled but this ins't an error (if, through overloading, there is another foo() function, enabled, that matches the call, obviously).并且I3std::enable_if_t的条件为true所以std::enable_if_t< I == 3, int>被替换为int所以foo()被启用但是当I不是3std::enable_if_t的条件std::enable_if_t如果为false所以std::enable_if_t< I == 3, int>未被替换,因此foo()未启用,但这不是错误(如果通过重载,还有另一个foo()函数已启用,显然与调用匹配)。

So where is the problem in your code?那么你的代码问题出在哪里呢?

The problem is that std::enable_if_t is substituted, when the first template parameter is true , with the second parameter.问题是std::enable_if_t在第一个模板参数为true时被替换为第二个参数。

So if you write所以如果你写

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

and you call然后你打电话

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

the std::is_floating_point<float>::value (but you can also use the shorter form std::is_floating_point_v<T> ( _v and not ::value )) so the substitution take place and you get std::is_floating_point<float>::value (但你也可以使用更短的形式std::is_floating_point_v<T>_v而不是::value ))所以替换发生,你得到

float = 0

but, unfortunately, a template value (not type) parameter can't be of type floating point, so you get an error.但是,不幸的是,模板值(不是类型)参数不能是浮点类型,因此您会收到错误消息。

If you use int instead of T , the substitution give you如果您使用int而不是T ,则替换为您提供

int = 0

and this is correct.这是正确的。

Another solution can be use the following form另一种解决方案可以使用以下形式

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

as suggested by Andreas Loanjoe, because the substitution give you正如 Andreas Loanjoe 所建议的,因为替换给你

typename = float

that is a valid syntax.这是一个有效的语法。

But this solution has the drawback that doesn't works when you want to write two alternative functions, as in the following example但是这个解决方案有一个缺点,当你想编写两个替代函数时不起作用,如下例所示

// 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 ()
 { }

where works the solution based on the value基于值的解决方案在哪里工作

// 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 ()
 { }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM