1

Consider:

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

Why doesn't the line marked as such compile? Shouldn't it perform implicit conversion using the supplied conversion operator into std::vector<>, so that the [] can be found?

There are (many) other questions on this site that ask variations on this question, but I couldn't find one that I think applies here. Does it have to do with std::vector being a template?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Roel
  • 19,338
  • 6
  • 61
  • 90
  • 2
    The expression `p2[0]` is actually the same as `p2.operator[](0)` (which should be possible to infer from the error messages). And the `Prop` class doesn't have an `operator[]` function. – Some programmer dude Oct 12 '17 at 08:43
  • So the problem is that p2[0] is considered one expression, right? Because if p2 were to be considered an expression on its own, the conversion to std::vector would be performed, and then .operator[](0) would be called on that, right? So is there a notationally convenient way to get Prop to behave the way I want - i.e. as a transparent container for some value of type T, along with some attributes (because in my real code, Prop has additional members, of course). – Roel Oct 12 '17 at 08:53
  • The usual way to wrap data like that is to overload the member access operator `->` or the dereference operator `*`. Then you could do something like `p2->at(0)`, or `(*p2)[0]`. It's more cumbersome, but still better than a `static_cast`. – Some programmer dude Oct 12 '17 at 08:56
  • Another common way is to use a "getter" function which returns the wrapped "value", which mean you could have `p2.get()[0]`. Which one to choose is a matter of personal preference, you could even use *all* these methods, and also keep the casting operator (but I recommend you return a *reference* instead, and add an overload that is `const` qualified, and do this (reference and `const`) for *all* access methods). – Some programmer dude Oct 12 '17 at 09:01

2 Answers2

4

Implicit conversions are not considered for objects of member function calls, including the subscript operator overload.

Consider the consequences if that were allowed: Every single time any undeclared member function is called like this, the compiler would have to figure out all the types that the object can be converted to (note that any other otherwise unrelated type could have a converting constructor), and check if that has declared the missing member function. Not to mention how confusing that would be for the reader of the code (the conversion might be obvious in your conversion operator case, but not in the converting constructor case, and as far as I know, they are not otherwise treated differently).

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

You would have to define a member function for each of the member function of vector that you want to pass pass through transparently. Here is the subscript operator as an example:

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

The problem is of course that all wrapped member functions must be explicitly declared. Another problem is arguments whose type depend on T. For example, vector::operator[] uses vector::size_type, which might not be defined for all T that you might use (certainly not for float). Here we make a compromise and use std::size_t.

A less laborious way of creating such "transparent" wrapper is inheritance. A publicly inheriting template would automatically have all the member functions of the parent and be implicitly convertible to it and can be referred by pointers and references of parent type. However, the transparency of such approach is a bit problematic mainly because ~vector is not virtual.

Private inheritance allows same wrapping as your member approach, but with much nicer syntax:

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

Note that inheritance approach prevents you from using fundamental types as T. Also, it makes the implicit conversion impossible (even with conversion operator) , so you cannot use Prop as T in free functions that expect T.


PS. Note that your conversion operator returns a value, so the vector must be copied, which may be undesirable. Consider providing reference versions instead:

operator T&&()&&           { return *this; }
operator T&()&             { return *this; }
operator const T&() const& { return *this; }
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Non-public inheritance, and publishing the desired parts of the interface with `using`, could be a solution to the inheritance thing. – Angew is no longer proud of SO Oct 12 '17 at 09:12
  • "The problem with this approach is of course, that then you cannot wrap just any type, but only those that provide the wrapped functions." Not quite. Since the wrapper is a template, the functions won't be instantiated unless called. So you *can* wrap types which don't support `[]`, you just can't apply `[]` to the wrapper in such case. – Angew is no longer proud of SO Oct 12 '17 at 09:13
  • @Angew about private inheritance: that has the same problem with wrapping the member object that all wrapped functions must be declared. The syntax for doing so is much nice though. – eerorika Oct 12 '17 at 09:15
1

the same way that any of the

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

don't make sense to compile

also

p2[0];

which is equivalent with

p2.operator[](0);

doesn't make sense to compile\


If you want this behavior (i.e. for Prop<T> to borrow T members) there is a C++ proposal by Bjarne to add the dot operator to the language. I would work the same way that operator -> works for smart pointers. AFAIR it had a lot of controversy and so I wouldn't hold my breath for it.

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

bolov
  • 72,283
  • 15
  • 145
  • 224