【问题标题】:Why do primitive and user-defined types act differently when returned as 'const' from a function?为什么原始类型和用户定义类型在从函数返回为“const”时表现不同?
【发布时间】:2017-10-03 12:54:39
【问题描述】:
#include <iostream>

using namespace std;

template<typename T>
void f(T&&) { cout << "f(T&&)" << endl; }

template<typename T>
void f(const T&&) { cout << "f(const T&&)" << endl; }

struct A {};
const A g1() { return {}; }
const int g2() { return {}; }

int main()
{
    f(g1()); // outputs "f(const T&&)" as expected.
    f(g2()); // outputs "f(T&&)" not as expected.
}

问题描述嵌入在代码中。我的编译器是clang 5.0。

我只是想知道:

在这种情况下,为什么 C++ 会以不同的方式对待内置类型和自定义类型?

【问题讨论】:

  • clang编译警告warning: 'const' type qualifier on return type has no effect [-Wignored-qualifiers]
  • 如果构建代码并启用所有警告(-Wall -Wextra on gcc),这些问题本可以避免。
  • @JonasWielicki:出于向后兼容性的原因,这不会启用所有警告,这就是 Clang 引入 -Weverything 的原因:)
  • @JonasWielicki:基本上,GCC 没有等价物。粗略地说 GCC -Wall 打开所有在引入时存在的警告,但没有任何新警告,因为担心这会阻止使用 -Wall -Werror 的第三方软件的人升级 GCC(这是一个非常有效的担忧,我不应该,看到 GCC 用于构建许多 Linux 发行版)。后来他们添加了-Wextra 来覆盖丢失的警告集,并且出于同样的原因它被稳定了,因此不包括之后包含的警告......
  • @MatthieuM。 GCC确实不时添加到-Wall 和-Wextra; -Wall 的实际政策类似于“几乎可以肯定表明存在错误,即使没有,也可以轻松更改代码以消除警告”,-Wextra 的实际政策类似,但估计的指示几率较低一个错误。您可能已经注意到,clang 的 -Weverything 包含警告,如果代码按原样正确,则没有解决方法 - 这是 GCC 人们想要避免的。

标签: c++ c++11 overloading standards overload-resolution


【解决方案1】:

我没有引用标准,但cppreference 证实了我的怀疑:

非类非数组纯右值不能是 cv 限定的。 (注意:函数调用或强制转换表达式可能会导致非类 cv 限定类型的纯右值,但 cv 限定符会立即被去除。)

返回的const int 只是一个普通的int prvalue,并且使非常量重载比const 更匹配。

【讨论】:

  • @alfC 因为const A 表示A 的所有成员都不能更改(即使您复制A),而对于int 甚至没有成员存在。
  • @alfC Looks like 是为了阻止人们用临时类对象做奇怪的事情。
  • 这就留下了为什么 C++ 允许类纯右值是 cv 限定的问题。恕我直言,这与允许它用于非类纯右值一样没有意义。
  • @cmaster 这有点道理,即它不允许您在类prvalue上调用非常量成员函数。我现在没有示例,但我想可以想出一些场景,例如stackoverflow.com/q/8716330/3093378。当然在 C++11 中不应该这样做,因为它会使移动语义停止工作。
  • @vsoftco 抱歉,我仍然看不到允许对类对象进行 cv 限定的意义:如果我正确阅读,prvalue 是具有值语义(!)的操作的瞬态结果,其中prvalue归调用操作的代码所有。恕我直言,限制是否允许调用者修改这样的值不是操作本身的业务。毕竟,调用它的析构函数最终销毁值是调用者的事情。根据定义,这是一个非常量操作,必须允许它避免创建不可删除的临时对象。
【解决方案2】:

为什么原始类型和用户定义类型在从函数返回为“const”时表现不同?

因为const 部分已从函数返回的原始类型中删除。原因如下:

在 C++11 中来自 § 5 Expressions [expr](第 84 页):

8

每当一个泛左值表达式作为一个运算符的操作数出现时 期望该操作数的右值,左值到右值(4.1), 数组到指针 (4.2) 或函数到指针 (4.3) 的标准转换是 应用于将表达式转换为纯右值。 [注意:因为 cv-qualifiers 从非类类型表达式的类型中删除 表达式被转换为纯右值,类型为左值表达式 例如,const int 可以用于类型为纯右值表达式的地方 int 是必需的。 ——尾注]

同样来自§ 5.2.3 Explicit type conversion (functional notation) [expr.type.conv](第 95 页):

2

表达式 T(),其中 T 是简单类型说明符或 typename-specifier 用于非数组完整对象类型或 (可能是 cv 限定的)void 类型,创建指定的纯右值 类型,它是 valueinitialized (8.5; 没有为 void() 情况)。 [注意:如果 T 是 cv 限定的非类类型,则 在确定结果的类型时忽略 cv 限定符 prvalue (3.10)。 ——尾注]

这意味着g2() 返回的const int prvalue 被有效地视为int。

【讨论】:

    【解决方案3】:

    引用标准,

    §8/6 Expressions [expr]

    如果纯右值最初的类型是“cv T”,其中 T 是 cv-unqualified 非类,非数组类型,表达式的类型 在任何进一步分析之前调整为 T。

    和§8/9 Expressions [expr]

    (强调我的)

    当一个泛左值表达式作为一个运算符的操作数出现时 期望该操作数的纯右值,左值到右值, 数组到指针或函数到指针的标准转换是 应用于将表达式转换为纯右值。 [ 注意:因为 从非类表达式的类型中删除 cv 限定符 表达式转换为纯右值时的类型,左值 例如,const int 类型的表达式可以用于纯右值 int 类型的表达式是必需的。 — 尾注 ]

    所以对于g2(),int是一个非类类型,而(的返回值)g2()是一个prvalue expression,那么const限定符被去掉,所以返回类型不是@ 987654331@,但int。这就是调用f(T&amp;&amp;) 的原因。

    【讨论】:

    【解决方案4】:

    前面的答案是完全有效的。我只是想添加一个潜在的动机,为什么有时返回 const 对象可能很有用。 在下面的例子中,class A 给出了来自class C 的内部数据的视图,在某些情况下这些数据是不可修改的(免责声明,为简洁起见,省略了一些重要部分——也有可能更简单的方法来实现此行为):

    class A {
        int *data;
        friend class C; // allow C to call private constructor
        A(int* x) : data(x) {}
        static int* clone(int*) {
            return 0; /* should actually clone data, with reference counting, etc */
        }
    public:
        // copy constructor of A clones the data
        A(const A& other) : data(clone(other.data)) {}
        // accessor operators:
        const int& operator[](int idx) const { return data[idx]; }
        // allows modifying data
        int& operator[](int idx) { return data[idx]; }
    };
    
    class C {
        int* internal_data;
    public:
        C() : internal_data(new int[4]) {} // actually, requires proper implementation of destructor, copy-constructor and operator=
        // Making A const prohibits callers of this method to modify internal data of C:
        const A getData() const { return A(internal_data); }
        // returning a non-const A allows modifying internal data:
        A getData() { return A(internal_data); }
    };
    
    int main()
    {
        C c1;
        const C c2;
    
        c1.getData()[0] = 1; // ok, modifies value in c1
        int x = c2.getData()[0]; // ok, reads value from c2
        // c2.getData()[0] = 2;  // fails, tries to modify data from c2
        A a = c2.getData(); // ok, calls copy constructor of A
        a[0] = 2; // ok, works on a copy of c2's data
    }
    

    【讨论】:

      猜你喜欢
      • 2021-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      相关资源
      最近更新 更多