Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alignment of multiple CRTP base classes

In CRTP, the base object can return a reference to the derived object via static cast.

Is this also true in the case of multiple inheritance? The second base and beyond might be at addresses different from the derived object. Consider for instance:

#include <iostream>
#include <string_view>

template<typename Derived>
struct Base1
{
    char c1;
};

template<typename Derived>
struct Base2
{
    char c2;
    auto& get2() const
    {
        return static_cast<const Derived&>(*this); // <-- OK?
    }
};

struct X : public Base1<X>, public Base2<X>
{
    X(std::string_view d) : data{d} {}

    std::string_view data;
};


int main()
{
    auto x = X{"cheesecake"};

    std::cout << x.get2().data << std::endl;
}

gcc's undefined behavior analyzer says this is undefined behavior.
clang's undefined behavior analyzer detects no problem.

Does the standard say which of them is right?

Update:
The bug in gcc has been fixed on trunk by now.

like image 641
Rumburak Avatar asked Dec 07 '20 07:12

Rumburak


1 Answers

Yes, this is defined, your code is OK. Multiple inheritance is the rare case in which the casted pointer differs from the original.

If you go to the Source:

[expr.static.cast.11] A prvalue of type “pointer to cv1 B”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a complete class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. ....

[expr.static.cast.2] An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. ...

Meaning that the cast you have used is valid and the same would work have you used pointers as long as you do not cast away any cv qualifiers.

To my best knowledge, this is a bug in the sanitizer which has trouble with casting references when they have to be redirected.

First, this has nothing to do with CRTP. The following does exactly the same, CRTP does that just automatically for us.

Base2<X>* base = &x;
const X* orig = static_cast<const X*>(base);

std::cout << &x << std::endl;
std::cout << base << std::endl;
std::cout << orig << std::endl;

Output:

0x7ffc7eeab4d0
0x7ffc7eeab4d1
0x7ffc7eeab4d0

Which is correct and gcc's sanitizer does not complain about anything.

But if you change pointers to references:

X x{"cheesecake"};

Base2<X>& base = x;
const X& orig = static_cast<const X&>(base);//Line 36

std::cout << &x << std::endl;
std::cout << &base << std::endl;
std::cout << &orig << std::endl;

Suddenly you get

0x7ffdbf87cf50
0x7ffdbf87cf51
0x7ffdbf87cf50

Program stderr

example.cpp:36:14: runtime error: reference binding to misaligned address 0x7ffdbf87cf51 for type 'const struct X', which requires 8 byte alignment
0x7ffdbf87cf51: note: pointer points here
 00 00 00  60 cf 87 bf fd 7f 00 00  0a 00 00 00 00 00 00 00  66 20 40 00 00 00 00 00  6d 19 40 00 00
              ^ 

Meaning that the output is again correct, but the sanitizer incorrectly does not redirect the reference when casting back.

like image 176
Quimby Avatar answered Sep 22 '22 12:09

Quimby