0

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.

wimalopaan
  • 4,838
  • 1
  • 21
  • 39
  • seems like you have your answer [here](https://stackoverflow.com/a/41125503/1632887) – seleciii44 Oct 31 '22 at 06:17
  • 1
    Surely that fact that you are printing the sizeof these objects prevents removal of the data member. It's not unused if it's part of a sizeof calculation. – john Oct 31 '22 at 06:21
  • 1
    As I know, the compiler is disallowed to remove the unused variable (as-if rule). See discussion https://stackoverflow.com/questions/23176564/can-unused-data-members-be-optimized-out-in-c and this [post](https://quuxplusone.github.io/blog/2020/12/02/unused-private-member/). Clang can warn about unused private data memer, but doesn't work here. – Nimrod Oct 31 '22 at 06:57
  • @wimalopaan I'm not certain but I think you are wrong, using sizeof does prevent the optimisation. Of course other things may be preventing the optimization as well. – john Oct 31 '22 at 07:15
  • *"But this solution is cumbersome."* You have several ways to handle the specialization (just the data field or the full class). – Jarod42 Oct 31 '22 at 08:30
  • @Nimrod: Do you know the reason why it is supported to access the private members via this so called "loophole": the explicit instantiation of templates? – wimalopaan Oct 31 '22 at 11:00
  • @wimalopaan: It allows (legit) custom specialization of `std::hash`. – Jarod42 Oct 31 '22 at 21:29
  • @wimalopaan That's another question lol. Simply put, the standard allows it. – Nimrod Nov 01 '22 at 01:58

2 Answers2

-1

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>] = {};
};
seleciii44
  • 1,529
  • 3
  • 13
  • 26
  • @JasonLiam in fact you are both wrong and right :) The first example (and also the original one) has it both 0, but the second/below one is different as you said. – seleciii44 Oct 31 '22 at 07:01
-1

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;
        }
    }   
};
Caleth
  • 52,200
  • 2
  • 44
  • 75