2

I am trying to create a conversion operator that would be explicit by default, except for some designated classes.

More precisely, I have a relatively simple class template whose instances should be convertible to some other type (int throughout this question, for simplicity). However, I want this conversion to be explicit by default, but still allow it to be implicit for one other class, namely the class passed as template argument. Without this last part, this is what it would look like:

template<typename T>
class A {
public:
    A(int i) : i(i) {}
    explicit operator int() const {
        return i;
    }

private:
    int i;
};

Now, I want to be able to write something like A a(2); int i = a; inside the methods of T (which is supposed to be a class).

My first idea was to make use of friend declarations and declare a private overload of the conversion operator. However, overloading based solely on explicit isn't allowed. So I tried using const for that instead, and it worked:

template<typename T>
class A {
public:
    A(int i) : i(i) {}
    explicit operator int() { // Public non-const
        return i;
    }

private:
    int i;

    operator int() const { // Private const
        return i;
    }

    friend T;
};

... until it doesn't. This works only when using non-const As, and while I don't plan on using const As, I still would like it to work in all cases. Note that if the public overload is const and the private one isn't, this works only using const As.

Here is a demo showing in which cases it works or doesn't.

I have thought of using volatile (demo), and while it makes it better, it still leads to the same problem: it only works when using non-volatile As.

Is there a better way to do that? More generally, is there a way to solve the problem in the very first sentence?

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
  • 1
    This really sounds like an [XY problem](https://en.wikipedia.org/wiki/XY_problem). Why should member functions of a particular class have different conversion semantics than usual, rather than different access permissions? What are you trying to achieve? – Sneftel Mar 10 '19 at 19:27
  • @Sneftel I don't get what you are saying. I'm trying to make member functions have different conversion semantics depending on access permissions. What I'm trying to achieve in practice in to drop the cast when inside a friend class. – Nelfeal Mar 10 '19 at 19:50
  • 1
    Access permissions don't determine what a particular block of code sees. They determine whether the compiler produces an error when a particular member is accessed. Think of them like a traffic cop: They can't stop you from speeding, they can just cite you after you do speed. Again, though: what are you trying to achieve? Why would it help you for some blocks of code to have different conversion rules from other blocks of code? – Sneftel Mar 10 '19 at 19:55
  • If an implicit conversion is wrong in one place in the code, then it rather likely is undesirable everywhere. I think the language is stopping you before you go too far down a dead end (the "Y" in Wikipedia's description of the XY problem). Don't try to drop the cast. It'll probably save you headaches later. _(Alternatively: what constructive purpose is served by dropping the cast?)_ – JaMiT Mar 10 '19 at 22:41
  • @Sneftel: You’re right about access not affecting overload resolution, but `explicit` and `static_cast` do. Given the general neglect of `volatile` objects (of class type), the version with a `volatile` `explicit` `private` conversion function that will never be selected for a `static_cast` seems to, surprisingly enough, work. – Davis Herring Mar 11 '19 at 01:36
  • @JaMiT Well I'm on the fence about that. Before trying the const/volatile hack, I thought the language would not allow this kind of behavior, or at least not through overloads (and I don't see another option). But after seeing the const/volatile hack work to some extent, I'm not sure what to think. Is it truly something I'm not supposed to want? For the moment, I'm using casts everywhere as you'd expect. I had hoped this question would bring me a new convenient tool (which is the "constructive purpose" you mention, and "what I'm trying to achieve" Sneftel asked about: convenience). – Nelfeal Mar 11 '19 at 09:46
  • It's not uncommon for one piece of code to have easier access to a particular thing. That's what `using namespace` is for, after all. But that tends to be on a per-block basis, not a per-class basis. (And note that even in a per-class basis, direct access to things like base class members are a convenience, not a fundamental design purpose, q.v. access to members of a dependent-type base class). It also tends to be on a pull basis rather than a push basis (you have to do `using namespace Foo`, rather than `Foo` deciding to inject itself into your function). – Sneftel Mar 11 '19 at 11:35
  • Depending on what you envision this being for, the answer might be as simple as "replace the explicit constructor with a static factory function". But the use case here is still so vague, I have no idea whether that fits it. – Sneftel Mar 11 '19 at 11:36
  • Given how little there is in the language to differentiate code in different classes, I think separating the question into a bare idea and a self-answer is in order. – Davis Herring Mar 12 '19 at 02:41
  • @DavisHerring Are you asking me to split the question into the first part and the partial answer? – Nelfeal Mar 12 '19 at 07:16
  • @Nelfeal: Yes, with just the `volatile` approach (with `const_cast`, which matters) as an answer: I don’t believe there’s a better way, and your trick, while somewhat disturbing, is effective. – Davis Herring Mar 12 '19 at 13:51
  • @Nelfeal What you call a "convenient tool" is probably better described as "allowing sloppy coding", which is not a constructive purpose. An implicit conversion from `A` to `int` means that it is safe for code to believe `A` is an integer. Marking the conversion `explicit` means that belief is not safe. How could this depend on where in the code you are? – JaMiT Mar 13 '19 at 00:25
  • @JaMiT If two classes `A` and `B` work closely together, you might want `A` to be a `friend` of `B`. It means `B` is supposed to know what `A` is and how it works. I don't see the problem with `B`'s code treating `A` as an `int` when convenient, while making it clear to the outside world that `A` isn't really an `int`. `B` could directly access `A::i` instead of using the conversion operator, while the outside world could not. To me, that's kind of the point of `friend` for classes, and I don't believe it qualifies as "allowing sloppy coding". – Nelfeal Mar 13 '19 at 06:56
  • 1
    @Nelfeal The usual reason to make a conversion explicit is to avoid accidentally invoking the conversion via a typo. Such accidents can be made by friends just as easily as they can be made by other code. Hence, allowing sloppy coding. (My point has nothing to do with knowing how a certain class works. It has to do with getting the compiler to stop you from making mistakes.) – JaMiT Mar 13 '19 at 08:43

1 Answers1

1

There is apparently no satisfying solution. The volatile option seems to be the most practical, and although it doesn't quite work with volatile As, an additional const_cast solves that problem.

template<typename T>
class A {
public:
    A(int i) : i(i) {}
    explicit operator int() const {
        return i;
    }

private:
    int i;

    operator int() const volatile {
        return static_cast<int>(const_cast<A const&>(*this));
        // or just
        // return i;
    }

    friend T;
};

Demo

As Sneftel stated in the comments, access permissions tend to be granted on a pull basis (using) rather than a push basis. That might be why the language does not provide a way to change conversion semantics depending on the class or block performing the conversions.

Nelfeal
  • 12,593
  • 1
  • 20
  • 39