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

问题描述 投票:2回答:3
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),因为我只想在它上面使用floating_types时才使用这个函数...它不能编译。

有人可以向我解释,为什么,甚至是什么呢?虽然我在这,但你怎么称之为这个功能?

SO上的每个教程或问题都会被答案轰炸,这很好,但是对于那些不了解正在发生的事情的人来说,即使是那些并不是真的有用。(sry,如果可能有点激动或激进)

编辑:我非常感谢现在的所有答案,我意识到我的措辞可能有点偏......我明白模板参数是什么,并且知道运行时和编译时间等之间的区别,但我不能得到一个好的掌握std::enable_if背后的语法

Aaditi:

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

这实际上是我唯一需要改变的东西。注意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();
}
c++ templates c++14 sfinae enable-if
3个回答
1
投票

我会尽量简单地解释这个问题,因为你要求它不要过多地进入语言细节。

模板参数是编译时参数(它们在应用程序的运行时不会更改)。函数参数是运行时并具有内存地址。

调用此函数看起来像这样:

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)

第三个函数调用失败的原因是因为该函数不存在。这是因为当作为其'template参数传入的编译时bool为false时,enable if导致函数不存在。

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

请注意,很多人都认为SFINAE语法很糟糕,并且随着C ++ 20中概念和约束的引入,不再需要很多SFINAE代码。


1
投票

首先,我将以工作形式重写您的功能

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

I3std::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))所以替换发生,你得到

float = 0

但是,遗憾的是,模板值(非类型)参数不能是浮点类型,因此会出错。

如果您使用int而不是T,则替换为您提供

int = 0

这是正确的。

另一种解决方案可以使用以下形式

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

正如Andreas Loanjoe所建议的那样,因为替换会给你

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

0
投票

我将采用自下而上的方法来解释有关模板的一些重要细节以及涉及哪些工具和技术,而不是从代码片段开始的自上而下的方法。


从本质上讲,模板是一种工具,可以让您编写适用于各种可能类型的C ++代码,而不是严格用于固定类型。在静态类型的语言中,这首先是一个很好的工具,可以在不牺牲类型安全的情况下重用代码,但在C ++中,模板非常强大,因为它们可以是专用的。

每个模板声明都以关键字template开头,以及类型或非类型(即值)参数的列表。类型参数使用特殊关键字typenameclass,用于让您的代码适用于多种类型。非类型参数只使用现有类型的名称,这些参数允许您将代码应用于编译时已知的一系列值。

一个非常基本的模板化函数可能如下所示:

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

现在,无论何时我们使用printT=Point2D,都会选择专业化。这非常有用,例如,如果通用模板对某个特定类型没有意义。

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

但是,如果我们想根据一个简单的条件同时为多种类型专门设计模板呢?这就是事情变得有点元的地方。首先,让我们尝试以允许在模板中使用它们的方式表达条件。这可能有点棘手,因为我们需要编译时的答案。

这里的条件是T是一个浮点数,如果T=floatT=double则为true,否则为false。实际上,仅使用模板专业化实现起来相当简单。

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

但是我们如何在另一个模板中使用这个编译时条件呢?当有许多可能的模板特化可供选择时,我们如何告诉编译器选择哪个模板?

这是通过利用名为SFINAE的C ++规则来实现的,该规则在基本英语中说,“当有许多可能的模板时,当前的模板没有意义*,只需跳过它并尝试下一个模板。”

*在尝试将模板参数替换为模板化代码时,会出现一个错误列表,导致模板被忽略而没有立即编译错误。列表有点long and complex

模板没有意义的一种可能方式是它是否尝试使用不存在的类型。

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

这是std::enable_if在引擎盖下使用的完全相同的技巧。 enable_if是接受类型Tbool条件的模板类,并且它包含仅在条件为真时等于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
};

现在我们有一个帮助器,我们可以用它来代替任何类型。如果我们传递的条件为true,那么我们可以安全地使用嵌套类型。如果我们传递的条件为false,则不再考虑模板。

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。这只是最终模板参数的默认值,它可以让你逃避指定TMKN但不是第五个参数。这里使用的enable_if意味着您可以提供其他名为fastor2d的模板,以及它们自己的条件集。

© www.soinside.com 2019 - 2024. All rights reserved.