为什么这个用户定义的转换没有完成?

Why is this user-defined conversion not done?

考虑:

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是模板有关系吗?

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

编译没有意义

还有

p2[0];

相当于

p2.operator[](0);

编译没有意义\


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

A bit of background for the operator dot proposal—Bjarne Stroustrup

Operator Dot (R3) - Bjarne Stroustrup, Gabriel Dos Rei

Smart References through Delegation: An Alternative to N4477's Operator Dot - Hubert Tong, Faisal Vali

Alternatives to operator dot - Bjarne Stroustrup

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

考虑一下如果允许这样做的后果:每次像这样调用任何未声明的成员函数时,编译器都必须找出对象可以转换为的所有类型(注意任何其他不相关的类型可以有一个转换构造函数),并检查它是否声明了缺少的成员函数。更不用说代码的 reader 会有多混乱(在您的转换运算符情况下,转换可能很明显,但在转换构造函数的情况下则不然,据我所知,它们没有得到其他处理不同)。

So is there a notationally convenient way to get Prop to behave the way I want

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

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.

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

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

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

请注意,继承方法会阻止您将基本类型用作 T。此外,它使隐式转换变得不可能(即使使用转换运算符),因此您不能在期望 T.

的自由函数中将 Prop 用作 T

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

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