Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why I'm not able to prevent the undesirable C-style cast to compile?

There is an undesirable C-style cast that I'm not able to prevent to compile. The undesirable cast performs a C-style cast from an object of some class to a non-const reference of some other class. The classes are unrelated. In the same time I like to support the C-style cast from an object of the same class to the const reference. I'm providing a public conversion operator to support the desirable cast. It seems that it is impossible to prevent the undesirable cast in this case.
The cast to non-const reference fails to build ("Sandbox::B::operator Sandbox::A &()" (declared at line 30) is inaccessible*), unfortunately cast to const reference either fails (error: more than one conversion function from "Sandbox::B" to "const Sandbox::A" applies: function "Sandbox::B::operator const Sandbox::A &()" function "Sandbox::B::operator Sandbox::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'm disabling operator operator A& () both desired and undesired conversions are build.

I'm using gcc, icc and MSVC compiles. I cannot control the client code and prevent there use of C-style cast.

like image 989
Lesh Avatar asked Oct 20 '14 10:10

Lesh


People also ask

Why is Static_cast better than C style cast?

In short: static_cast<>() gives you a compile time checking ability, C-Style cast doesn't. static_cast<>() is more readable and can be spotted easily anywhere inside a C++ source code, C_Style cast is'nt. Intentions are conveyed much better using C++ casts.

Can I use Static_cast in C?

Static casts are only available in C++.

Which of the following is C style type casting?

C-style typecasts are available in both C and C++, but are considered poor C++ style because they are not as noticeable or precise as the C++ casts. C-style casts can be used to convert any type into any other type, potentially with unsafe results (such as casting an integer into a pointer type).

Is static cast done at compile time?

Static Cast: This is the simplest type of cast which can be used. It is a compile time cast.It does things like implicit conversions between types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones).


1 Answers

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 is related to the rules of the C-Style cast:

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

a) const_cast(expression)

b) static_cast(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambigous non-virtual base

c) static_cast (with extensions) followed by const_cast

d) reinterpret_cast(expression)

e) reinterpret_cast followed by const_cast

The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled

Disclaimer: This explanation is based on guesses mostly, there are multiple steps and complex rules so i'm not sure everything really works as i think i've understood it but here you go.

Since you cast to a reference, reinterpret_cast will always works based on its rules of type aliasing, so the only way to make that C-Style cast fail is to make a static_cast on that type unambiguously produce an error. Unfortunately the conversion rules doesn't seem to consider a user defined conversion to const type to be a better match than a user defined conversion to a non cv-qualified type, they are both on the same level even if the static_cast target type is const qualified. Whereas with templates, SFINAE and parameter deduction kicks in and with some magic compiler powder extracted from a mountain dragon, it works. (yeah this step is a little more mysterious to me too).

like image 159
Drax Avatar answered Sep 20 '22 19:09

Drax