0

Is the following code valid C++? Otherwise, is there a valid way to simultaneously interpret memory as values of different type?

#include <cstdio>
struct Base { int payload; };
struct D1 : Base { void operator()(){ printf("D1: %d\n", payload);} };
struct D2 : Base { void operator()(){ printf("D2: %d\n", payload);} };

int main()
{
   D1 d1;
   D2& d2 = static_cast<D2&>(static_cast<Base&>(d1));
   d1();
   d2();
   d2.payload = 3;
   d1();
   d2();
}

In response to @NickoPo: My use case is basically what follows. Imagine that IntBase is not necessarily cheap to copy, that there are many complex algorithms, some of which profit from numbers being prime vs. odd, and others don't:

#include <cassert>
#include <cstdio>

bool is_odd(int value) { return 0 != value % 2; }
bool is_small_prime(int value) { return 2 == value || 3 == value || 5 == value || 7 == value; }

class IntBase
{
   public:
      explicit IntBase(int value) : m_value(value) {}
      int value() const { return m_value; }
   protected:
      int m_value;
};

class OddInt : public IntBase
{
   public:
      explicit OddInt(int value) : IntBase(value) { assert(is_odd(m_value)); }
};

class SmallPrimeInt : public IntBase
{
   public:
      explicit SmallPrimeInt(int value) : IntBase(value) { assert(is_small_prime(m_value)); }
};

bool is_constrainable_to_odd_int(IntBase const& x)
{
   return is_odd(x.value());
}

OddInt const& constrain_to_odd_int(IntBase const& x)
{
   assert(is_odd(x.value()));
   return static_cast<OddInt const&>(x);
}

bool is_constrainable_to_small_prime_int(IntBase const& x)
{
   return is_small_prime(x.value());
}

SmallPrimeInt const& constrain_to_small_prime_int(IntBase const& x)
{
   assert(is_small_prime(x.value()));
   return static_cast<SmallPrimeInt const&>(x);
}

void algorithm(IntBase const&)
{
   printf("algoritm(IntBase const&)\n");
}

void algorithm(OddInt const&)
{
   printf("algoritm(OddInt const&)\n");
}

void algorithm(SmallPrimeInt const&)
{
   printf("algoritm(SmallPrimeInt const&)\n");
}

void test(IntBase const& x)
{
   if (is_constrainable_to_small_prime_int(x))
   {
      algorithm(constrain_to_small_prime_int(x));
   }
   else if (is_constrainable_to_odd_int(x))
   {
      algorithm(constrain_to_odd_int(x));
   }
   else
   {
      algorithm(x);
   }
}

void test(OddInt const& x)
{
   if (is_constrainable_to_small_prime_int(x))
   {
      algorithm(constrain_to_small_prime_int(x));
   }
   else
   {
      algorithm(constrain_to_odd_int(x));
   }
}

int main()
{
   IntBase x(0);
   OddInt y(1);
   OddInt z(7);

   test(x); // algoritm(IntBase const&)
   test(y); // algoritm(OddInt const&)
   test(z); // algoritm(SmallPrimeInt const&)
}

Related:

Community
  • 1
  • 1
precarious
  • 598
  • 2
  • 14
  • `D1` is not a `D2` and `D2` is not a `D1`...this will turn into undefined behavior if you try to access any elements through `d2`. – Captain Obvlious Jun 22 '15 at 19:21
  • Well, now I am interested why would you want to treat a single instance as two separate classes? – Nicko Po Jun 22 '15 at 19:25
  • @Nicko Po I can imagine adding constraints to D1 and D2 such that, say, D1 represents odd numbers and D2 represents prime numbers. After checking if a given value of type D1 is prime once, I could cast it to D2 and pass it to algorithms specialized for prime numbers without checking the condition again. – precarious Jun 22 '15 at 19:37
  • @CaptainOblivious: the C++11 standard does have some provision for "common initial subsequence" for PODs or the like, although I never remember the exact terms of the concessions (it may be that you are allowed to do this stuff only in unions or something like that). – Matteo Italia Jun 22 '15 at 19:51
  • @precarious, you can easily use `std::true_type` and `std::false_type` to accomplish that goal. – R Sahu Jun 22 '15 at 19:53
  • @R Sahu I certainly could use an 'unintrusive' approach. I would need one flag per argument and keep arguments and flags in sync. That sounds error prone. – precarious Jun 22 '15 at 20:15
  • @MatteoItalia that only applies when the two structs are members of the same union – M.M Jun 22 '15 at 22:10
  • @precarious I'd suggest that you're probably over-using inheritance if it seems that you need to do this. – M.M Jun 22 '15 at 22:12
  • @MattMcNabb I don't need to do it and I don't need to do it this way. I'm just curious if there's a way to interpret the same data at the same memory location in different ways. I chose inheritance for no particular reason other than it seems to work, i.e. it compiles without warning using clang++ 3.6.1 and does what I hoped for. That's not enough, however. What I'd like to know is whether this behaviour is defined or if there are valid alternatives. Can you tell me more about the approach involving structs as members of a union? – precarious Jun 23 '15 at 15:56

2 Answers2

1

If you're going to cast while using similar interfaces while using type as a guarantee, I'd recommend that you just wrap the inner data with your new object type and then provide access to the inner data in order to transfer it from one type to another. There's no point in doing static casting or reinterpret casting if you're not going to do it safely.

Here's an example:

http://coliru.stacked-crooked.com/a/40d5efeff22fcdcd

#include <iostream>

//Base data structure to encapsulate only data.
struct data {
    data(int i) : i(i) {}

    int i;   
};

//Wrapper around our data structure, with interfaces to access
//the values and the data; implement your own constructor to
//gate the value
class PrimeInt {
public:
    PrimeInt(const int i)
    : d(i) {}

    PrimeInt(const data& other)
    : d(other) {}

    PrimeInt(data&& other)
    : d(std::move(other)) {}

    PrimeInt& operator=(const PrimeInt&) = default;
    PrimeInt& operator=(PrimeInt&&) = default;

    int get() {return d.i;};

    operator data() {return d;};

private:
    data d;
};

//Wrapper around our data structure, with interfaces to access
//the values and the data; implement your own constructor to
//gate the value
class OddInt {
public:
    OddInt(const int i)
    : d(i) {}

    OddInt(const data& other)
    : d(other) {}

    OddInt(data&& other)
    : d(std::move(other)) {}

    OddInt& operator=(const OddInt&) = default;
    OddInt& operator=(OddInt&&) = default;

    int get() {return d.i;};

    operator data() {return d;};

private:
    data d;
};

//Notice that we can now implicitly cast from one type to another.
int main() {
    PrimeInt pi(10);

    std::cout << pi.get() << std::endl;

    OddInt oi(pi);

    std::cout << oi.get() << std::endl;

    return 0;
}
CinchBlue
  • 6,046
  • 1
  • 27
  • 58
  • Yes, I could do that, thank you. Your solution requires copying (or moving), however. This is what I am trying to avoid. – precarious Jun 22 '15 at 21:17
  • @precarious Why would you try to avoid either? They're both boilerplate but they don't really change much about the philosophy of the program. – CinchBlue Jun 22 '15 at 21:20
  • The special member functions , other than the `int` constructor, could all have been omitted entirely (and so the defaulted ones would apply) – M.M Jun 22 '15 at 22:12
  • @VermillionAzure What I'm interested in is certainly a niche, and it's irrational, most probably. It's just about 'Why should I make a copy if the exact data is already there?' – precarious Jun 23 '15 at 15:43
  • @precarious That's why they invented the move constructor and defined value semantics; one of the outcomes is that you can "steal" resources from another object by "moving" it out using std::move. – CinchBlue Jun 23 '15 at 17:51
  • @VermillionAzure I'm aware of move semantics. What you propose is clean and works but it has restrictions. Moving resources from an object modifies this object. You cannot move from a constant object. – precarious Jun 23 '15 at 18:03
0

If your objects are not cheap to copy, you are probably passing pointers or references everywhere. You can wrap pointers to your common base in different class types and pass them by value. That is, instead of this (pseudocode)

class B
class D1 : B { ... }
class D2 : B { ... }

D1* d1; D2* d2;

you have

class Bimpl 

class B { Bimpl* bimpl; }
class D1 : B { ... }
class D2 : B { ... }

D1 d1; D2 d2;

Here you never do any built-in cast. If you want to convert D1 to D2, you write your own conversion function.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243