为什么不进行用户定义的转换

Why is this user-defined conversion not done?

本文关键字:转换 定义 用户 为什么不      更新时间:2023-10-16

考虑:

template<typename T>
struct Prop
{
T value;
operator T() { return value; }
};
int main()
{
Prop<float> p1 { 5 };
Prop<std::vector<float>> p2 { { 1, 2, 3 } };
float f1 = p1;              // Works fine
float f2_1_1 = p2.value[0]; // Works fine
float f2_1_2 = p2[0];       // Doesn't compile
return 0;
}

为什么标记为这样的行没有编译?难道它不应该使用提供的转换运算符将std::vector<>隐式转换为[]吗?

这个网站上还有(许多)其他问题对这个问题提出了不同的要求,但我找不到一个我认为适用于这里的问题。是否与std::vector作为模板有关?

成员函数调用的对象不考虑隐式转换,包括下标运算符重载。

如果允许的话,请考虑后果:每次像这样调用任何未声明的成员函数时,编译器都必须找出对象可以转换为的所有类型(请注意,任何其他不相关的类型都可能有转换构造函数),并检查它是否声明了丢失的成员函数。更不用说这会让代码的读者感到多么困惑(转换在转换运算符的情况下可能是显而易见的,但在转换构造函数的情况下则不然,据我所知,它们在其他方面没有区别对待)。

那么,有没有一种方便的方法可以让Prop按照我想要的表现

您必须为要透明传递的向量的每个成员函数定义一个成员函数。下面是下标运算符的例子:

auto operator[](std::size_t pos) {
return value[pos];
}
auto operator[](std::size_t pos) const {
return value[pos];
}

当然,问题是所有封装的成员函数都必须显式声明。另一个问题是参数的类型取决于T。例如,vector::operator[]使用vector::size_type,这可能不是为您可能使用的所有T定义的(当然不是为float定义的)。在这里,我们做出妥协,使用std::size_t

创建这种"透明"包装器的一种不那么费力的方法是继承。一个公共继承模板将自动拥有父模板的所有成员函数,并且可以隐式转换为它,并且可以由父类型的指针和引用引用。然而,这种方法的透明度有点问题,主要是因为~vector不是虚拟的。

私有继承允许与成员方法相同的包装,但语法要好得多:

template<typename T>
struct Prop : private T
{
using T::operator[];
using T::T;
};

请注意,继承方法防止您将基本类型用作T。此外,它使隐式转换变得不可能(即使使用转换运算符),因此在需要T的自由函数中不能将Prop用作T


PS。请注意,转换运算符返回一个值,因此必须复制向量,这可能是不希望的。考虑提供参考版本:

operator T&&()&&           { return *this; }
operator T&()&             { return *this; }
operator const T&() const& { return *this; }

与任何

p2.size();
p2.begin();
p2.push_back(24);
// etc.

编译没有意义

还有

p2[0];

相当于

p2.operator[](0);

编译\没有意义


如果您想要这种行为(即Prop<T>借用T成员),Bjarne提出了一个C++建议,将点运算符添加到语言中。我的工作方式与运算符->处理智能指针的工作方式相同。AFAIR有很多争议,所以我不会为此屏住呼吸。

运营商网点提案的一点背景——Bjarne Stroustrup

操作员点(R3)-Bjarne Stroustrup,Gabriel Dos Rei

授权智能参考:N4477运营商Dot-Hubert Tong,Faisal Vali 的替代方案

操作员点的替代品-Bjarne Stroustrup