Why can't I prevent unwanted C-style compilation?

There is an unwanted C style that I cannot prevent to compile. An unwanted listing performs a C style cast from an object of some class to a non-const reference of some other class. The classes are not related. At the same time, I like to maintain a C-style cast from an object of the same class to a constant reference. I am providing a public transform operator to support the desired actor. It seems that in this case it is impossible to prevent unwanted selection.
Casting to an unconnected reference cannot be built ("Sandbox :: B :: operator Sandbox :: A & ()" (declared on line 30) is not available *), unfortunately used for a const reference or crash (error: more than one conversion function is applied from "Sandbox :: B" to "Const Sandbox :: A": function "Sandbox :: B :: const operator Sandbox :: A & ()" function "Sandbox :: B: : Sandbox operator :: A & () "):

#include <iostream>
#include <string>
#include <cstdlib>

namespace Sandbox {
    class A {
    public:
        A (int i) : _x (i) { }
    private:
        int _x;
    };

    class B {
    public:
        B (const char* m) : _m (m), _a (std::atoi (m)) { }

        /*
         * This one shall be supported.
         */ 
        operator const A& () {
            return _a;
        }
    private:
        /*
         * This one shall be not supported.
         * If this one is disabled both desired and undesired conversions pass the compilation.
         */ 
        operator A& ();

        const std::string _m;
        const A _a;
    };
}

int main () {
    Sandbox::A a (1973);
    Sandbox::B b ("1984");

    /*
     * This is the undesirable cast and it shall fail to compile.
     */
    (Sandbox::A&)b;
    /*
     * This is the desirable cast and it shall pass the compilation.
     */
    (const Sandbox::A&)b;

    return 0;
}

      

If I disable the operator operator A& ()

, both desired and unwanted transformations are generated.

I am using gcc, icc and MSVC compilations. I cannot control the client code and prevent C-style from being used.

+3


source to share


1 answer


This should do the trick (tested on clang3.5):

#include <iostream>
#include <string>
#include <cstdlib>

namespace Sandbox {
  class A {
  public:
    A (int i) : _x (i) { }

    void        fun()
    {
      std::cout << "action" << std::endl;
    }

  private:
    int _x;
  };

  class B {
  public:
    B (const char* m) : _m (m), _a (std::atoi (m)) { }

    /*
     * This one shall be supported.
     */
    template<typename T, typename Enable = typename std::enable_if<std::is_same<T, A>::value, A>::type>
    operator const T& ()
    {
      return _a;
    }

    /*
     * This one shall be not supported.
     * If this one is disabled both desired and undesired conversions pass the compilation.
     */
  private:
    template<typename T, typename Enable = typename std::enable_if<std::is_same<T, A>::value, A>::type>
    operator T& ();

    const std::string _m;
    const A _a;
  };
}

int main () {
  Sandbox::A a (1973);
  Sandbox::B b ("1984");

  /*
   * This is the undesirable cast and it shall fail to compile.
   */
  (Sandbox::A&)b;

  /*
   * This is the desirable cast and it shall pass the compilation.
   */
  (const Sandbox::A&)b;

  return 0;
}

      


As for why your version doesn't do what you want, it has to do with the C-Style application rules:



When a C-style expression expression is encountered, the compiler attempts the following cast expressions in the following order:

a) const_cast (expression)

b) static_cast (expression), with extensions: a pointer or reference to a derived class is also allowed to be cast into a pointer or referenced to an unambiguous base class (and vice versa), even if the base class is not available (i.e. this frame is ignored private inheritance specifier). The same applies to point to member pointer to member pointer unambiguous non-virtual base

c) static_cast (with extensions) by const_cast

d) reinterpret_cast (expression)

f) reinterpret_cast followed by const_cast

The first choice that meets the requirements of the corresponding casting operator is chosen even if it cannot be compiled

Disclaimer: This explanation is based on guesswork mostly, there are several steps and tricky rules, so I'm not sure if everything really works as I think I figured it out, but here you go.

Since you are referencing a link, reinterpret_cast

will always work based on the type alias rules , so the only way to do this C-Style cast fail is to make static_cast

it unambiguously throw an error on that type. Unfortunately, the conversion rules don't seem to think that a user-defined conversion to a type const

should be better than a user-converted conversion to an unqualified type, they are both on the same level even if static_cast

the target type is const

. Whereas with templates, SFINAE and parameter deduction kick in and with some magic compiler powder extracted from mountain dragon it works. (yes, this step is a little more mysterious to me).

+3


source







All Articles