C++模板参数类型推导

Dom*_*tos 10 c++ templates c++11

给定模板化函数,如下所示:

template<class T>
int Function(T object);
Run Code Online (Sandbox Code Playgroud)

用户可以通过指定模板化类型来调用此函数,如下所示:

int result = Function<float>(100.f); // Valid
Run Code Online (Sandbox Code Playgroud)

但是类型规范是可选的,因为编译器可以从提供的参数的类型推导出T的类型; 像这样:

int result = Function(100.f); // Also valid, the compiler deduced the type "float" from the literal's type
Run Code Online (Sandbox Code Playgroud)

假设我有点复杂,我想要一个这样的模板化值参数:

template<class T, T* object>
int Function();
Run Code Online (Sandbox Code Playgroud)

我可以这样调用我的函数:

static float val = 100.f;
// ...
int result = Function<float, &val>();
Run Code Online (Sandbox Code Playgroud)

我的问题是:有没有办法我强迫编译器根据参数类型和val来推断类型T?

我需要的是一种使以下代码有效的方法:

static float val = 100.f;
// ...
int result = Function<&val>();
Run Code Online (Sandbox Code Playgroud)

可以吗?

Yak*_*ont 11

在C++ 17中,您可以使用auto非类型模板参数.这将让您解决您的问题.

像这样的东西:

template<auto object, class T=std::decay_t<decltype(*object)>>
int Function();
Run Code Online (Sandbox Code Playgroud)

(假设你想要T体内的类型Function)

在C++ 14中,缺少C++ 17功能.它的添加完全是因为它丢失了.解决方法涉及宏等#define UGLY_HACK(...) decltype(__VA_ARGS__), __VA_ARGS__.


ggu*_*lia 7

编辑:这里的答案来自有效的现代C++

这是容易摆出但难以回答的问题之一!我记得读过关于模板类型演绎的整章,对于一个菜鸟读者,答案在一次阅读中也不清楚.不过我会在这里澄清一下.

应该注意的是,有一些叫做Universal References (与引用或r值引用不同)影响模板类型推导,我假设读者知道l值和r值引用.

任何无处不在的函数模板定义如下所示:

template <typename T>
returnType function(paramType param);
Run Code Online (Sandbox Code Playgroud)

对函数的调用会看起来像这样:

function(expression);
Run Code Online (Sandbox Code Playgroud)

编译器使用表达式来确定T的类型和paramType的类型.这是因为更常见的是paramType包含const,const&,const &&等装饰.初学者会倾向于认为编译器推导出的类型T表达式的类型相同,即传递给函数的参数,但是情况并非总是如此.类型T的扣除取决于表达式paramType.根据函数参数paramType的不同,有三种情况需要考虑模板类型推导:

  1. paramType是指针或引用,但不是通用引用
  2. paramType是一个通用引用
  3. paramType既不是指针也不是引用.

让我们逐一看看每个案例

情况1:paramType是指针或引用,但不是通用引用

叫我疯了,但这是最简单的案例.在这种情况下,类型推导的工作方式如下:(i)如果表达式是引用,则忽略引用部分(ii)然后将表达式的模式与paramType匹配以确定T

让我们来看一个例子:

template <typename T>
returnType function(T &param);
Run Code Online (Sandbox Code Playgroud)

我们有以下变量声明:

int x = 23;               // x is int
const int const_x = x;    // const_x is const int
const int& ref_x = x;     // ref_x is a reference to x as const int
Run Code Online (Sandbox Code Playgroud)

在各种呼叫中推断出对Tparam的调用如下:

f(x);                    //T is int, param's type is int&
f(const_x);              //T is const int, param's type is const int&
f(ref_x);                //T is const int, param's type is const int&
Run Code Online (Sandbox Code Playgroud)

这里有两点需要注意:

(i)编译器在此处忽略了引用类型的类型推导

(ii)当传递const对象或引用const对象时,const-ness成为T类的一部分,因此将const对象或对const对象的引用传递给参数T&的函数是安全的.

如果我们将函数参数从T&更改为const T&,则事情会发生一些变化.因为在这种情况下我们假设param是对const对象的引用,所以不再需要将const作为T的一部分推导出来.以下是一个例子:

template <typename T>
returnType function(const T& param);  // param is now a ref-to-const

int x = 23;                    // same as previous
const int const_x = x;         // same as previous
const int& ref_x = x;          // same as previous

f(x);                         // T is int, paramType is const int&
f(const_x);                   // T is int, paramType is const int&
f(ref_x);                     // T is int, paramType is const int&
Run Code Online (Sandbox Code Playgroud)

如果paramType是一个指针,那么事情将基本上与引用相同,只是会有指针而不是引用.以下为了完整起见提供:

template <typename T>
returnType function( T* paramType);  //paramType is now a pointer

int x = 23;                      // same as before
const int *pointer_x = &x;       // pointer_x is pointer to x as const int

f(&x);                          // T is int, paramType is int*
f(&pointer_x);                  // T is const int, paramType is const int*
Run Code Online (Sandbox Code Playgroud)

在r值引用的情况下,类型TparamType推导基本上遵循与引用的情况相同的规则.

这涵盖了第一种情况的大部分内容.让我们来看看我们的案例2.

案例2:paramType是一个通用引用

通用引用被声明为r值引用但是采用l值但是它们的行为不同的是函数参数接收l值引用.以下是类型推导如何适用于此案例:

(i)如果表达式是l值,则TparamType都推导为l值.(面对代码的样子,这看起来很奇怪,因为虽然使用r值引用的语法声明了paramType,但它的推导类型是l值引用.)应该注意的是,这是T的唯一情况.被推断为参考.

以下示例澄清了我的解释:

template <typename T>
returnType function(const T* paramType);

int x = 23;                      // same as before
const int *pointer_x = &x;       // pointer_x is pointer to x as const int

f(&x);                          // T is int, paramType is const int*
f(&pointer_x);                  // T is int, paramType is const int*
Run Code Online (Sandbox Code Playgroud)

我想在这里说实话,并说这并不能解释为什么通用引用按照它们的方式工作,但我认为如果我继续在这里证明这一点,这篇文章会变得太长.

案例3:paramType既不是指针也不是引用##

这就是模板中的值传递的地方,这意味着param将是传递给调用函数的参数的任何副本的副本,即完全是一个新对象,这会激发控制T的类型推导的规则.表达.这里要注意的两点是:

(ⅰ)忽略refrence在-ness 表达,如果有恰好是一个.

(ⅱ)忽略后REF -ness,忽略常量 -ness或挥发性 -ness太,即,如果存在

template <typename T>
returnType function(T&& paramType);  //param becomes universal reference if
                                // argument to function call is an l-value

int x = 23                     // same as previous
const int const_x = x;         // same as previous
const int& ref_x = x;          // same as previous

f(x);             // x is l-value therefore T is int&
                  // paramType is int&

f(const_x);       // const_x is l-value therefore T is const int&
                  //paramType is also const int&

f(ref_x);        // ref_x is l-value therefore T is const int&
                 //paramType is also const int&

f(23);          // 27 is r-value so T is int
                // paramType is now int&&
Run Code Online (Sandbox Code Playgroud)

请注意,即使const_x和ref_x是无法修改的const对象,也不意味着它们的副本无法修改.这看起来很简单,但是当我们将常量指针传递给常量对象时它会变得棘手.让我们看看另一个例子:

template <typename T>
returnType function(T paramType);

int x = 23;
const int const_x = x;
const int& ref_x = x;

f(x);             // T and paramType are both int
f(const_x);       // T and paramType are both int here too
f(ref_x);         // T and paramType are both int again
Run Code Online (Sandbox Code Playgroud)

const指针按值传递时,const -ness会丢失并且指针会按值复制,这​​与传递值的类型推导规则同步,但指针指向的const -ness会被保留,因此在paramType将是const*双.

当我开始了解它时,这可能会让你头脑旋转.最好的方法是重新阅读并尝试编写代码.

  • 这几乎是有效的现代 C++ 中的内容。 (2认同)
  • 这并不能回答问题 (2认同)