auto和decltype的关系

发布于 2024-11-26 22:42:16 字数 225 浏览 3 评论 0 原文

或两者都不是

auto x = initializer;

相当于

decltype(initializer) x = initializer;

decltype((initializer)) x = initializer;

Is

auto x = initializer;

equivalent to

decltype(initializer) x = initializer;

or

decltype((initializer)) x = initializer;

or neither?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

动听の歌 2024-12-03 22:42:16

decltype 还会考虑表达式是 rvalue 还是 lvalue

维基百科说

decltype 表示的类型可以与 auto 推导的类型不同。

#include <vector>
int main()
{
    const std::vector<int> v(1);
    auto a = v[0];        // a has type int
    decltype(v[0]) b = 1; // b has type const int&, the return type of
                        // std::vector<int>::operator[](size_type) const
    auto c = 0;           // c has type int
    auto d = c;           // d has type int
    decltype(c) e;        // e has type int, the type of the entity named by c
    decltype((c)) f = c;  // f has type int&, because (c) is an lvalue
    decltype(0) g;        // g has type int, because 0 is an rvalue
}

这几乎解释了重要的区别。注意 decltype(c)decltype((c)) 不一样!

有时 autodecltype 以协作方式一起工作,例如下面的示例(取自 wiki,并进行了一些修改):

int& foo(int& i);
float foo(float& f);

template <class T>
auto f(T& t) −> decltype(foo(t)) 
{
  return foo(t);
}

维基百科进一步 解释了decltype的语义如下:

与 sizeof 运算符类似,decltype 的操作数未求值。通俗地说,decltype(e) 返回的类型推导如下:

  • 如果表达式 e 引用本地或命名空间范围内的变量、静态成员变量或函数参数,则结果是该变量或参数的声明类型
  • 如果 e 是函数调用或重载运算符调用,则 decltype(e) 表示该函数声明的返回类型
  • 否则,如果 e 是左值,则 decltype(e) 为 T&,其中 T 是 e 的类型;如果 e 是右值,则结果为 T

这些语义旨在满足通用库编写者的需求,同时对于新手程序员来说也很直观,因为 decltype 的返回类型始终与源代码中声明的对象或函数的类型完全匹配。更正式地说,规则 1 适用于不带括号的 id 表达式和类成员访问表达式。对于函数调用,推导类型是静态选择的函数的返回类型,由重载解析规则确定。示例:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4; // type is const double&

decltype 的后两次调用之间存在差异的原因是带括号的表达式 (a->x) 既不是 id 表达式也不是成员访问表达式,因此并不表示命名对象因为表达式是左值,所以它的推导类型是“对表达式类型的引用”,或者const double&。

decltype also considers whether the expression is rvalue or lvalue .

Wikipedia says,

The type denoted by decltype can be different from the type deduced by auto.

#include <vector>
int main()
{
    const std::vector<int> v(1);
    auto a = v[0];        // a has type int
    decltype(v[0]) b = 1; // b has type const int&, the return type of
                        // std::vector<int>::operator[](size_type) const
    auto c = 0;           // c has type int
    auto d = c;           // d has type int
    decltype(c) e;        // e has type int, the type of the entity named by c
    decltype((c)) f = c;  // f has type int&, because (c) is an lvalue
    decltype(0) g;        // g has type int, because 0 is an rvalue
}

That pretty much explains the imporant difference. Notice decltype(c) and decltype((c)) are not same!

And sometime auto and decltype works together in a cooperative way, such as in the following example (taken from wiki, and modified a bit):

int& foo(int& i);
float foo(float& f);

template <class T>
auto f(T& t) −> decltype(foo(t)) 
{
  return foo(t);
}

Wikipedia further explains the semantics of decltype as follows:

Similarly to the sizeof operator, the operand of decltype is unevaluated. Informally, the type returned by decltype(e) is deduced as follows:

  • If the expression e refers to a variable in local or namespace scope, a static member variable or a function parameter, then the result is that variable's or parameter's declared type
  • If e is a function call or an overloaded operator invocation, decltype(e) denotes the declared return type of that function
  • Otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e; if e is an rvalue, the result is T

These semantics were designed to fulfill the needs of generic library writers, while at the same time being intuitive for novice programmers, because the return type of decltype always matches the type of the object or function exactly as declared in the source code. More formally, Rule 1 applies to unparenthesized id-expressions and class member access expressions. For function calls, the deduced type is the return type of the statically chosen function, as determined by the rules for overload resolution. Example:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1; // type is const int&&
decltype(i) x2; // type is int
decltype(a->x) x3; // type is double
decltype((a->x)) x4; // type is const double&

The reason for the difference between the latter two invocations of decltype is that the parenthesized expression (a->x) is neither an id-expression nor a member access expression, and therefore does not denote a named object.Because the expression is an lvalue, its deduced type is "reference to the type of the expression", or const double&.

你的心境我的脸 2024-12-03 22:42:16

这行不通(而且很难看):

decltype([]() { foo(); }) f = []() { foo(); };

auto f = []() { foo(); };

会。

This won't work (and is ugly):

decltype([]() { foo(); }) f = []() { foo(); };

whereas

auto f = []() { foo(); };

will.

冬天的雪花 2024-12-03 22:42:16

这取决于。 autodecltype 具有不同的用途,因此它们不会一对一映射。

auto 的规则最容易解释,因为它们与模板参数推导相同。我不会在这里详细介绍它们,但请注意 auto&auto&& 也是一些可能的用途!

然而,decltype 有几种情况,其中一些您在上面已经说明过(信息和引用取自 n3290,7.1.6.2 简单类型说明符 [dcl.type.simple]),我将它们分为两类

  • :使用标准所称的“无括号的 id 表达式或无括号的类成员访问”
  • 其余部分!

非正式地,我想说 decltype 可以对名称(第一种情况)或表达式进行操作。 (正式地,根据语法 decltype 对表达式进行操作,因此将第一种情况视为细化,将第二种情况视为包罗万象。)

当使用带有 decltype 的名称时,您将得到 <该实体的 em> 声明类型。例如,decltype(an_object.a_member) 是类定义中出现的成员类型。另一方面,如果我们使用 decltype( (an_object.a_member) ) ,我们会发现自己处于包罗万象的情况,并且我们正在检查表达式的类型,因为它会出现在代码。

因此,如何涵盖您的问题的所有情况:

int initializer;
auto x = initializer; // type int
// equivalent since initializer was declared as int
decltype(initializer) y = initializer;

enum E { initializer };
auto x = initializer; // type E
// equivalent because the expression is a prvalue of type E
decltype( (initializer) ) y = initializer;

struct {
    int const& ializer;
} init { 0 };
auto x = init.ializer; // type int
// not equivalent because declared type is int const&
// decltype(init.ializer) y = init.ializer;
// not equivalent because the expression is an lvalue of type int const&
// decltype( (init.ializer) ) y = init.ializer;

It depends. auto and decltype serve different purposes so they don't map one-to-one.

The rules for auto are the easiest to explain because they are the same as for template parameter deduction. I won't expand on them here, but note that auto& and auto&& are also some possible uses!

decltype however has several cases, some of which you have illustrated above (information and quotes taken from n3290, 7.1.6.2 Simple type specifiers [dcl.type.simple]) that I separate into two categories:

  • when using what the Standard calls "an unparenthesized id-expression or an unparenthesized class member access"
  • the rest!

Informally, I'd say that decltype can operate on either names (for the first case) or expressions. (Formally and according to the grammar decltype operates on expressions, so think of the first case as a refinement and the second case as a catch-all.)

When using a name with decltype, you get the declared type of that entity. So for instance decltype(an_object.a_member) is the type of the member as it appears in the class definition. On the other hand, if we use decltype( (an_object.a_member) ) we find ourselves in the catch-all case and we're inspecting the type of the expression as it would appear in code.

Accordingly, how to cover all the cases of your questions:

int initializer;
auto x = initializer; // type int
// equivalent since initializer was declared as int
decltype(initializer) y = initializer;

enum E { initializer };
auto x = initializer; // type E
// equivalent because the expression is a prvalue of type E
decltype( (initializer) ) y = initializer;

struct {
    int const& ializer;
} init { 0 };
auto x = init.ializer; // type int
// not equivalent because declared type is int const&
// decltype(init.ializer) y = init.ializer;
// not equivalent because the expression is an lvalue of type int const&
// decltype( (init.ializer) ) y = init.ializer;
七禾 2024-12-03 22:42:16

auto

auto 很简单:它将给出与按值模板参数推导相同的类型。 auto 在表达式上统一工作。

template <class T>
void deduce(T x);

int &refint();
std::string str();
std::string const conststr();

auto i1 = 1; // deduce(1) gives T=int so int i1
auto i2 = i1; // deduce(i1) gives T=int so int i2
auto i3 = refint(); // deduce(refint()) gives T=int so int i3
const auto ci1 = i1; // deduce(i1) gives T=int so const int ci1
auto i4 = ci1; // deduce(ci1) gives T=int so int i4

auto s1 = std::string(); // std::string s1
auto s2 = str(); // std::string s2
auto s3 = conststr(); // std::string s3

在 C++ 中,表达式不能具有引用类型(refint() 的类型为 int 而不是 int&)。

请注意,表达式的左值性对于右侧表达式(等号右侧或一般被复制的内容)来说不是问题。右值 1 的处理方式与左值 i1refint() 类似。

对于按值参数(即非引用参数),不仅应用左值到右值的转换,还应用数组到指针的转换。 const 被忽略。

decltype

decltype 是一个非常有用的功能,但接口却很糟糕:

decltype 对根据名称查找和其他表达式定义的某些表达式的作用不同!这就是让人讨厌 C++ 的特性类型。

名为

decltype(entity) 的东西的 decltype 将进行名称查找并给出实体的声明类型。 (entity 可以是非限定或限定标识符,或者是成员访问,例如 expr.identifier。)

decltype(f(args)) 将进行名称查找和重载解析,并给出函数声明的返回类型,而不是表达式的类型:

extern decltype(refint()) ri1; // int &ri1

所以现在我可以使用 decltype 检查我对语言的理解:

template <class T, class U>
struct sametype {};

template <class T>
struct sametype<T,T> {typedef int same;};

sametype::same 存在 iff TU 是完全相同的类型。

sametype<decltype (i1), int>::same check_i1;
sametype<decltype (i2), int>::same check_i2;
sametype<decltype (i3), int>::same check_i3;
sametype<decltype (i4), int>::same check_i4;

sametype<decltype (ci1), const int>::same check_ci1;
sametype<decltype (ir1), int&>::same check_ir1;

sametype<decltype (s1), std::string>::same check_s1;
sametype<decltype (s2), std::string>::same check_s2;
sametype<decltype (s3), std::string>::same check_s3;

编译良好,所以我没有错!

其他表达式的 decltype

否则,其中 expr 未根据名称查找定义(不是上述情况之一),例如表达式(expr)decltype 实现了一个独特的功能(但 C++ 设计者不会在其上花费其他关键字。)

decltype(expr) 将给出用它修饰的表达式的类型lxrvalueness (lvalue/xvalue/prvalue-ness):

  • T 类型的纯右值(纯右值)给出 T
  • T 类型的 xvalue 给出 T&&
  • T 类型的左值给出 T&

这是函数调用规则的倒数:如果 f 是一个函数带返回类型

  • T&,表达式 f() 是左值
  • T&&,表达式 f() 是xvalue
  • 裸类型(纯对象类型,非引用)T,表达式 f() 是纯右值

,也可用于强制转换:对于裸类型(纯对象类型,非引用)参考)T

  • (T&)expr 是左值
  • (T&&)expr 是 xvalue
  • (T)expr 是右值

引用性是将 lxrvalueness 编码为类型。 decltype 进行此编码是为了保存和传输事物的 lxrvalueness。

当您想要为表达式添加别名时,这非常有用:作为函数调用的表达式 expr 的 lxrvalueness(普通 f(args) 或使用运算符语法,例如 >a @ b) 与声明为 decltype(expr) alias();alias() 的 lxrvalueness 相同,

这可以用于纯转发在通用代码中:

// decorated type of an expression
#define EXPR_DEC_TYPE(expr) decltype((expr))

int i;
int &ri = i;
int fi();
int &fri();

EXPR_DEC_TYPE(i) alias_i = i; // int &
EXPR_DEC_TYPE(ri) alias_ri = ri; // int &

EXPR_DEC_TYPE(fi()) alias_fi(); // int alias_fi()
EXPR_DEC_TYPE(fri()) alias_fri(); // int &alias_fri()

请注意EXPR_DEC_TYPE(foo()) 在设计上等于 declexpr(foo())(在大多数情况下),但计算不同:

  • declexpr(foo (args))foo 进行名称查找,并进行重载
    解析,找到声明,返回准确的返回类型
    声明,故事结束

  • EXPR_DEC_TYPE(foo(args)) 然后找到声明的类型
    计算

    • 表达式的类型 T,它是裸露的返回类型(不带
      参考)

    • 根据引用的表达式的 lxrvalueness LXR
      声明的返回类型:引用的左值,右引用的x值...

    然后用LXR修饰类型T以获得decT类型:

      如果 LXR = 纯右值,

    • decTT
      如果 LXR = xvalue
    • decTT&&
      如果 LXR = 左值,
    • decTT&

    EXPR_DEC_TYPE 返回 decT,与声明的返回类型相同。

auto

auto is simple: it will give the same type as by-value template parameter deduction. auto works uniformly on expressions.

template <class T>
void deduce(T x);

int &refint();
std::string str();
std::string const conststr();

auto i1 = 1; // deduce(1) gives T=int so int i1
auto i2 = i1; // deduce(i1) gives T=int so int i2
auto i3 = refint(); // deduce(refint()) gives T=int so int i3
const auto ci1 = i1; // deduce(i1) gives T=int so const int ci1
auto i4 = ci1; // deduce(ci1) gives T=int so int i4

auto s1 = std::string(); // std::string s1
auto s2 = str(); // std::string s2
auto s3 = conststr(); // std::string s3

In C++ expressions cannot have reference type (refint() has type int not int&).

Note that the lvalueness of the expression is not an issue on an expression on the right (on the right of equal sign, or something which is being copied in general). The rvalue 1 is treated like the lvalues i1 and refint().

For by value parameters (that is, non reference parameters), not only lvalue to rvalue conversion is applied, but also array to pointer conversions. const is ignored.

decltype

decltype is a very useful feature with an horrible interface:

decltype acts differently on some expressions defined in term of name lookup and other expressions! This is the type of features that make people hate C++.

decltype of something named

decltype(entity) will do name lookup and give the declared type of the entity. (entity can be an unqualified or qualified identifier, or a member access such as expr.identifier.)

decltype(f(args)) will do name lookup and overload resolution and give the declared return type of the function, not the type the expression:

extern decltype(refint()) ri1; // int &ri1

So now I can check my understanding of the language with decltype:

template <class T, class U>
struct sametype {};

template <class T>
struct sametype<T,T> {typedef int same;};

sametype<T,U>::same exists iff T and U are exactly the same type.

sametype<decltype (i1), int>::same check_i1;
sametype<decltype (i2), int>::same check_i2;
sametype<decltype (i3), int>::same check_i3;
sametype<decltype (i4), int>::same check_i4;

sametype<decltype (ci1), const int>::same check_ci1;
sametype<decltype (ir1), int&>::same check_ir1;

sametype<decltype (s1), std::string>::same check_s1;
sametype<decltype (s2), std::string>::same check_s2;
sametype<decltype (s3), std::string>::same check_s3;

compiles fine, so I was not wrong!

decltype of other expressions

Otherwise, where expr is not defined in term of name lookup (not one of the above cases), such as the expression (expr), decltype implements a distinct feature (but the C++ designers would not spent another keywords on it.)

decltype(expr) will give the type of the expression decorated with its lxrvalueness (lvalue/xvalue/prvalue-ness):

  • prvalue (pure rvalue) of type T gives T
  • xvalue of type T gives T&&
  • lvalue of type T gives T&

This is the reciprocal of function call rule: if f is a function with return type

  • T&, the expression f() is an lvalue
  • T&&, the expression f() is an xvalue
  • naked type (pure object type, non reference) T, the expression f() is a prvalue

and also for casts: for naked type (pure object type, non reference) T

  • (T&)expr is a lvalue
  • (T&&)expr is an xvalue
  • (T)expr is a prvalue

Reference-ness is the encoding of lxrvalueness into types. decltype does this encoding to save and transfer the lxrvalueness of things.

This is useful when you want to alias an expression: the lxrvalueness of an expression expr that is a function call (either normal f(args) or with operator syntax like a @ b) is the same as the lxrvalueness of alias() declared as decltype(expr) alias();

This can be used for pure forwarding in generic code:

// decorated type of an expression
#define EXPR_DEC_TYPE(expr) decltype((expr))

int i;
int &ri = i;
int fi();
int &fri();

EXPR_DEC_TYPE(i) alias_i = i; // int &
EXPR_DEC_TYPE(ri) alias_ri = ri; // int &

EXPR_DEC_TYPE(fi()) alias_fi(); // int alias_fi()
EXPR_DEC_TYPE(fri()) alias_fri(); // int &alias_fri()

Note that EXPR_DEC_TYPE(foo()) equals declexpr(foo()) by design (in most cases), but the computations are different:

  • declexpr(foo(args)) does name lookup for foo, does overload
    resolution, finds the declaration, returns the exact return type
    declared, end of story

  • EXPR_DEC_TYPE(foo(args)) finds the type of the declaration then
    computes

    • the type T of the expression which is the return type naked (without
      reference)

    • the lxrvalueness LXR of the expression according to the referenceness
      of the declared return type: lvalue for reference, xvalue of r-reference...

    then it decorates the type T with LXR to obtain the decT type:

    • decT is T if LXR = prvalue
    • decT is T&& if LXR = xvalue
    • decT is T& if LXR = lvalue

    EXPR_DEC_TYPE returns decT which is the same as the declared return type.

金兰素衣 2024-12-03 22:42:16
  1. 如果 initializer 是一个数组,则 decltype(x)decltype((x))
    不要简单地致力于它。然而 auto 将被推导为
    指针。
  2. 如果 initializer 是一个函数,那么应用 decltype(fp)
    然而,推导出函数类型, auto 将推导出其
    返回类型。

因此,一般来说 auto 不能被视为替代您要求的任何 decltype() 版本。

  1. If initializer is an array then decltype(x) or decltype((x))
    don't work simply on it. However auto will be deduced to a
    pointer.
  2. If initializer is a function then applying decltype(fp) will
    deduce to the function type however, auto will deduce to its
    return type.

So in general auto cannot be considered as a replacement of any decltype() version you asked.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文