How to remove unused data-members?

153 Views Asked by At

The following code contains an example template X, where the data-member is unused, if the template is parametrized with other type than A. But the sizes of the objects a and b are the same, even with -O3, so the optimizer does not remove the unused data-member x2.

#include <iostream>
#include <cstdint>
#include <type_traits>

struct A {};
struct B {};

template<typename T>
struct X {
    int value() const {
        if constexpr(std::is_same_v<T, A>) {
            return x1 + x2;
        }
        else {
            return x1;
        }
    }
private:
    int x1{0};    
    int x2{0};    
};

int main() {
    X<A> a;
    X<B> b;
    
    std::cout << sizeof(a) << '\n';
    std::cout << sizeof(b) << '\n';
    return a.value() + b.value();
}

Now there are two questions:

  1. Is the optimizer not allowed (as-if-rule) to remove the unused data-member? Why?
  2. How to achieve the goal: that the class X<B> does not contain the unused data-member x2?

There is a workaround with a base-class template and a specialisation for A that contains the data-member x2. But this solution is cumbersome. I wonder if there is a solution without using a base class?

Edit: I don't think that using the sizeof() operator prevents the optimization:

//#include <iostream>
#include <cstdint>
#include <type_traits>

struct A {};
struct B {};

template<typename T>
struct X {
    int value() const {
        if constexpr(std::is_same_v<T, A>) {
            return x1 + x2;
        }
        else {
            return x1;
        }
    }
private:
    int x1{0};    
    int x2{1};    
};

X<A> a;
X<B> b;


int main() {
//    std::cout << sizeof(a) << '\n';
//    std::cout << sizeof(b) << '\n';
    return a.value() + b.value();
}

If you look a the assembly (e.g. compiler explorer) you see that the instances contain in both cases both data-members.

2

There are 2 best solutions below

1
seleciii44 On

Here is an ugly solution if x1 and x2 has the same type:

template<typename T>
struct X {
    int value() const {
        if constexpr(std::is_same_v<T, A>) {
            return x1() + x2();
        }
        else {
            return x1();
        }
    }

    int x1() const {
        return x[0];
    }

    int x2() const {
        return x[1];
    }

private:
    //int x1{0};    
    //int x2{0};
    int x[1 + std::is_same_v<T, A>] = {};
};
0
Caleth On

Is the optimizer not allowed (as-if-rule) to remove the unused data-member?

Just because an implementation can do something, doesn't mean that it does do that thing. And the act of observing the size of your type might prompt the implementation to choose not to omit the "unused" data member.

It is a relatively common idiom to have unused data members to ensure a particular size and layout of a data type, so implementations are likely to take at face value the declarations in a type.

How to achieve the goal: that the class X does not contain the unused data-member x2?

There are numerous ways, including moving the data members to a base class, and inherit conditionally.

namespace detail {

    struct XA {
        int x1{0};    
        int x2{0};    
    };
    
    struct XB {
        int x1{0};  
    };   
}

template<typename T>
struct X : private std::conditional_t<std::is_same_v<T, A>, detail::XA, detail::XB>{
    int value() const {
        if constexpr(std::is_same_v<T, A>) {
            return x1 + x2;
        }
        else {
            return x1;
        }
    }   
};