티스토리 뷰

카테고리 없음

Filament C++ 관련

newpolaris 2021. 3. 17. 10:27

friend template decl. & impl.

TVecHelpers.h 파일에는 아래와 같은 구현이 존재한다

TVecHelpers.h

TVecAddOperators class 이고, TVec type이 이를 상속 받는다

private:
    /*
     * NOTE: the functions below ARE NOT member methods. They are friend functions
     * with they definition inlined with their declaration. This makes these
     * template functions available to the compiler when (and only when) this class
     * is instantiated, at which point they're only templated on the 2nd parameter
     * (the first one, BASE<T> being known).
     */

    template<typename U>
    friend inline constexpr
    VECTOR<arithmetic_result_t<T, U>> MATH_PURE operator+(const VECTOR<T>& lv, const VECTOR<U>& rv) {
        VECTOR<arithmetic_result_t<T, U>> res(lv);
        res += rv;
        return res;
    }

friend decl. 와 동시에 구현을 제공,

이는 전통적으로 외부에 2항 연산자 operator+를 제공하는 것과 동일하게 동작하는 듯

empty_bases

VS 2019 16.7.0 에서는 아직도 다중 상속일 경우 크기가 늘어나는듯,

c++:/lastest를 써도 마찬가지로 보인다

template <typename T>
struct TVecAdd 
{
};

template <typename T>
struct TVecMul
{
};

template <typename T>
class TVec4 : public TVecAdd<T>, TVecMul<T>
{
public:
    typedef T value_type;

    static constexpr size_t SIZE = 4;

    T v[SIZE];
};


static_assert(sizeof(TVec4<float>) != 4 * sizeof(float), "size error");

VS 에서만 아래와 같은 empty_bases 처리가 필요함

// #   define MATH_EMPTY_BASES __declspec(empty_bases)
template <typename T>
class MATH_EMPTY_BASES TVec4 : public TVecAdd<T>, TVecMul<T>
{
public:
    typedef T value_type;

    static constexpr size_t SIZE = 4;

    T v[SIZE];
};

constexpr_init

vec4.h 파일에는 아래 매크로가 존재한다

  union {
        T v[SIZE] MATH_CONSTEXPR_INIT;
  };

compiler.h 파일에는 해당 내역에 대한 주석이 존재한다.

// About value initialization, the C++ standard says:
//   if T is a class type with a default constructor that is neither user-provided nor deleted
//   (that is, it may be a class with an implicitly-defined or defaulted default constructor),
//   the object is zero-initialized and then it is default-initialized
//   if it has a non-trivial default constructor;
// Unfortunately, MSVC always calls the default constructor, even if it is trivial, which
// breaks constexpr-ness. To workaround this, we're always zero-initializing TVecN<>
#   define MATH_CONSTEXPR_INIT {}
#   define MATH_DEFAULT_CTOR {}
#   define MATH_DEFAULT_CTOR_CONSTEXPR constexpr

테스트를 위해, 아래와 같이 코드 작성 후 MSVC에서 테스트 함.

template <typename T> struct TVecAdd {}; 
template <typename T> struct TVecMul {};

template <typename T>
class __declspec(empty_bases) TVec4 : public TVecAdd<T>, TVecMul<T>
{
public:
    typedef T value_type;
    static constexpr size_t SIZE = 4;
    T v[SIZE];

    TVec4() noexcept = default;
};
static_assert(sizeof(TVec4<float>) == 4*sizeof(float), "size error");

using vec4f = TVec4<float>;

int main()
{
    vec4f v0;
    // warning C4269: 'v1': 'const' automatic data initialized with compiler generated default constructor produces unreliable results
    // error C2737: 'v1': 'constexpr' object must be initialized
    constexpr vec4f v1;

    return 0;
}

저상태로 초기화 하려면, 아래와 같은 코드가 필요함.

T v[SIZE] {};
constexpr TVec4() noexcpt {}

이건 다른 컴파일러에서도 마찬가지였는데,

이유가 먼가했더니

test_vec.cpp 파일내에선, {} 과 함꼐 사용했다.

constexpr vec4 v1 {};

그런데 해당 초기화룰 수행할 경우,

아래와 같은 코드에도 문제 없이 돌아가는거 같은데?

TVec4() noexcept = default;

MSVC 옛날 버전 때문인지 테스트 필요

nonactive union member 관련

inactive union 란, 무엇일까?

https://en.cppreference.com/w/cpp/language/union

The union is only as big as necessary to hold its largest data member. The other data members are allocated in the same bytes as part of that largest member. The details of that allocation are implementation-defined but all non-static data members will have the same address (since C++14). It's undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union.
#include <iostream>
#include <cstdint>
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};                      // the whole union occupies 4 bytes

int main()
{
    S s = {0x12345678}; // initializes the first member, s.n is now the active member
    // at this point, reading from s.s or s.c is undefined behavior
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s is now the active member
    // at this point, reading from n or c is UB but most compilers define it
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}

보통 저렇게 쓰는 거 아닌가 했지만, 엄격히는 undefined behavior 라고 한다.

할당된 변수가 actvie가 되고, 나머진 inactive가 되는 듯

대부분의 컴파일러가 지원한다고 하지만,

constexpr 표현식에서 gcc/clang/msvc 에서 에러를 표시한다

constexpr의 엄격한 검사루틴이 에러라고 표시하는 것 같다.

int main()
{

    typedef float T;
    constexpr size_t SIZE = 4;
    union S {
        struct {
            union { T x; };
            union { T y; };
            struct { T z, w; };
        };
        T v[SIZE];
    };

    constexpr S s = { 0, 1, 2, 3 };
    // error C2131: expression did not evaluate to a constant
    //  : failure was caused by accessing a non-active member of a union
    //  : see usage of 'main::S::v'
    constexpr int a = s.v[0];

    return 0;
}

대입은 union 중 위의 변수를 초기화한다.

그랬음에도, constexpr 로 v 변수를 접근하니 위와 같은 오류가 난다.

이러한 체크는, constexpr로 선언된 함수를 따라가면서 이루어지는데,

template <typename T> struct TVecAdd {}; 
template <typename T> struct TVecMul {};

template <typename T>
class __declspec(empty_bases) TVec4 : public TVecAdd<T>, TVecMul<T>
{
public:
    typedef T value_type;
    static constexpr size_t SIZE = 4;
    union {
        struct {
            union { T x; };
            union { T y; };
            struct { T z, w; };
        };
        T v[SIZE];
    };

    // constexpr TVec4() noexcept : v{ 0.f, 0.f, 0.f, 0.f } {}
    constexpr TVec4() noexcept : x(0.f), y(0.f), z(0.f), w(0.f) {}

    template <typename X, typename Y>
    constexpr TVec4(X x, Y y) noexcept : x(x), y(y), z(x), w(y) {}

    template <typename U, typename V>
    friend inline constexpr TVec4<U> operator+(const TVec4<U>& a, const TVec4<V>& b) noexcept {
        TVec4<U> ret(a.x+b.x, a.y+b.y);
        // failure was caused by accessing a non-active member of a union
        // TVec4<U> ret(a.v[0]+b.v[0], a.v[1]+a.v[1]);
        return ret;
    }
};
static_assert(sizeof(TVec4<float>) == 4*sizeof(float), "size error");

using vec4f = TVec4<float>;

int main()
{
    vec4f v0;
    constexpr vec4f v1;
    constexpr vec4f v2;
    constexpr vec4f v3 = v1 + v2;

    return 0;
}

operator+ 에 들어가는 인자 v1, v2는 생성자를 보면 x, y를 각각 초기화 했다.

이 경우, 초기화를 v 로 한게 아니라서, x, y로 access 해야 에러가 나지 않는다.

clang은 아래와 같이 에러 발생한다

https://godbolt.org/z/qd1Wse


#1 with x86-64 clang 10.0.0
<source>:45:21: error: constexpr variable 'v3' must be initialized by a constant expression
    constexpr vec4f v3 = v1 + v2;
                    ^    ~~~~~~~
<source>:32:22: note: read of member 'v' of union with active member '' is not allowed in a constant expression
        TVec4<U> ret(a.v[0]+b.v[0], a.v[1]+a.v[1]);
                     ^
<source>:45:29: note: in call to 'operator+(v1, v2)'
    constexpr vec4f v3 = v1 + v2;
                            ^
1 error generated.
Compiler returned: 1

만약, constexpr mat4() noexcept {} 같은 코드를 쓰려면,

union을 없애거나 첫번째 요소에 {} 를 추가해야 된다.

union 의 첫 요소를 추가한것 처럼 컴파일러에 알려줘야하기 때문

template <typename T>
class TMat4
{
public:
    // 
    union {
        TVec4<T> v[4];
    };

    // error: 'constexpr' constructor for union 'TMat4<float>::<unnamed union>' must initialize exactly one non-static data member
    // clang pass, gcc, msvc error
    constexpr explicit TMat4() noexcept {}

};

또, 이상한 오류인데

union, struct 의 조합으로 swizzling을 흉내낸 부분의 변수를

초기화 할 때 사용할 경우, 해당 class를 멤버로 두는 class에서 에러가 발생

msvc 에서는 에러, godbolt 에서는 msvc에서 워닝으로 표시되는데? 머지?

#include <cstdint>
#include <cstddef>


#ifdef _MSC_VER
#   define MATH_EMPTY_BASES __declspec(empty_bases)
#else
#   define MATH_EMPTY_BASES 

#endif

template <typename T> struct TVecAdd {}; 
template <typename T> struct TVecMul {};

template <typename T>
class MATH_EMPTY_BASES TVec4 : public TVecAdd<T>, TVecMul<T>
{
public:
    typedef T value_type;
    static constexpr size_t SIZE = 4;
    union {
        T v[SIZE]{};
        /*
        : warning C4789: buffer 'm' of size 68 bytes will be overrun; 4 bytes will be written starting at offset 76
: warning C4789: buffer 'm' of size 68 bytes will be overrun; 4 bytes will be written starting at offset 72
: warning C4789: buffer 'm' of size 68 bytes will be overrun; 4 bytes will be written starting at offset 68 */
        #if 0
        struct {
            union { T x; };
            union {
                struct {
                    union { T y; };
                    union {
                        struct { T z, w; };
                    };
                };
            };
        };
        #else
        struct { T x, y, z, w; };
        #endif
    };

    constexpr TVec4() noexcept : x(0.f), y(0.f), z(0.f), w(0.f) {}

    template <typename X, typename Y>
    constexpr TVec4(X x, Y y) noexcept : x(x), y(y), z(0.f), w(0.f) {}

    template <typename U, typename V>
    friend constexpr TVec4<U> operator+(const TVec4<U>& a, const TVec4<V>& b) noexcept {
        TVec4<U> ret(a.x+b.x, a.y+b.y);
        // failure was caused by accessing a non-active member of a union
        //TVec4<U> ret(a.v[0]+b.v[0], a.v[1]+a.v[1]);
        return ret;
    }
};

using vec4f = TVec4<float>;

template <typename T>
class TMat44
{
public:
    constexpr TMat44() noexcept {}

    union {
        struct { TVec4<T> x, y, z, w; };
        TVec4<T> col[4] {};
    };
};

int main()
{
    vec4f v0;
    constexpr vec4f v1;
    constexpr vec4f v2;
    constexpr vec4f v3 = v1 + v2;

    constexpr TMat44<float> m;

    return 0;
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크