【问题标题】:The relationship between auto and decltypeauto 和 decltype 的关系
【发布时间】:2011-10-15 17:47:17
【问题描述】:

auto x = initializer;

相当于

decltype(initializer) x = initializer;

decltype((initializer)) x = initializer;

或者两者都没有?

【问题讨论】:

    标签: c++ c++11 auto type-inference decltype


    【解决方案1】:

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

    Wikipedia says,

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

    维基百科进一步explains the semanticsdecltype 如下:

    与 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(operator-expression) 不再查看 FDIS 中静态声明的返回类型。
    • @JohannesSchaub-litb 你能举个例子吗?
    • @curiousguy 在const int operator+(A, B)void(&amp;&amp;operator+(A, B))();。当使用静态声明的返回类型时,首先会产生const intvoid(&amp;&amp;)(),并且检查表达式类型和值类别会产生intvoid(&amp;)()
    • @JohannesSchaub-litb 好的,这 void(&amp;&amp;)() 的东西是......微妙的。没想到。
    • @Nawaz 在你的代码 sn-p auto f(T&amp; t) −&gt; decltype(foo(t)){} 中,是否等同于decltype(auto) f(T&amp; t) {} ??
    【解决方案2】:

    这行不通(而且很丑):

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

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

    会的。

    【讨论】:

      【解决方案3】:

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

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

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

      • 使用标准所称的“无括号的 id 表达式或无括号的类成员访问”时
      • 剩下的!

      非正式地,我想说decltype 可以对names(对于第一种情况)或表达式 进行操作。 (形式上并根据语法decltype 对表达式进行操作,因此将第一种情况视为一种改进,而将第二种情况视为一个包罗万象的情况。)

      当使用带有 decltype 的名称时,您将获得该实体的 已声明 类型。因此,例如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;
      

      【讨论】:

        【解决方案4】:

        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&amp;)。

        请注意,表达式的左值性不是右侧表达式的问题(在等号右侧,或一般被复制的东西)。右值 1 被视为左值 i1refint()

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

        decltype

        decltype 是一个非常有用的功能,但界面很糟糕:

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

        decltype 的东西命名

        decltype(entity) 将进行名称查找并给出实体的声明类型。 (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&lt;T,U&gt;::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;
        

        compiles fine,所以我没看错!

        decltype 的其他表达式

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

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

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

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

        • T&amp;,表达式f()是一个左值
        • T&amp;&amp;,表达式 f() 是一个 xvalue
        • 裸类型(纯对象类型,非引用)T,表达式f()是prvalue

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

        • (T&amp;)expr 是一个左值
        • (T&amp;&amp;)expr 是一个极值
        • (T)expr 是prvalue

        Reference-ness 是将 lxrvalueness 编码为类型。 decltype 做这种编码是为了保存和传输事物的 lxrvalueness。

        当您想为表达式取别名时,这很有用:作为函数调用的表达式 expr 的 lxrvalueness(普通 f(args) 或像 a @ b 这样的运算符语法)与 @ 的 lxrvalueness 相同987654373@ 声明为decltype(expr) alias();

        这可以用于泛型代码中的纯转发:

        // 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根据引用度 声明的返回类型:lvalue for reference, xvalue of r-reference...

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

          • decTT 如果 LXR = prvalue
          • decTT&amp;&amp; 如果 LXR = xvalue
          • decTT&amp; 如果 LXR = 左值

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

        【讨论】:

          【解决方案5】:
          1. 如果initializer 是一个数组,那么decltype(x)decltype((x)) 不要简单地工作。但是auto 将被推导出为 指针。
          2. 如果initializer 是一个函数,那么应用decltype(fp) 将 然而,推导出函数类型,auto 将推导出它的 返回类型。

          所以一般auto 不能被视为替代您询问的任何decltype() 版本。

          【讨论】:

          • @downvoter 请注意解释。我说错了吗?
          • 我没有投反对票,但在您的第二点中,您提到auto var = f 中的var 类型(其中fint f())是int。但是,由于您没有调用该函数,var 的实际类型是int (*)()。而decltype(f()) 确实会解析为int。看到它here
          • @Vitus,我提到了decltype(f) 而不是decltype(f());我在发布之前已经检查过了。见演示:ideone.com/fIh13;这种否决票仍然没有道理;这是可悲的。 :(
          • 是的,但您无法比较 auto a = f();(您调用函数的位置)和decltype(f) a;(您不调用的位置)。这是我的第一条评论的重点。
          • 您是说auto x = printf 将是int。但事实并非如此。 @Vitus 说了什么。不确定该 ideone 代码是为了演示什么。
          猜你喜欢
          • 1970-01-01
          • 2017-08-02
          • 1970-01-01
          • 1970-01-01
          • 2021-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-12
          相关资源
          最近更新 更多