How to conditionally declare a local variable based on a template argument?

Viewed 276

I would like to conditionally declare a local variable in a function, based on a template bool parameter. So if it is true it should be there, otherwise shouldn't be there in the sense that I don't want that variable to allocate memory on the stack or call its constructor. It could also be a basic type.

I cannot declare it within the constexpr if block because I need persistence between the usages.

  1. I can just declare the variable and add [[maybe_unused]]. Then, is there a compiler optimization which guaranties not to allocate memory for the variable?
template <bool T> void foo()
{
[[maybe_unused]] SomeLargeClass x;
if constexpr(T)
{
... do something with x
}

... do something without x

if constexpr(T)
{
... do something more with x
}

}
  1. I tried to replace the declaration with
std::enable_if_t<T, SomeLargeClass> x;

but it doesn't work because the T==false case fails to provide a type. Why is this not SFINAE?

  1. Do I have any other options?
5 Answers

As-if rule might discard unused SomeLargeClass, but it is more complicated if that class do allocations. One easy trade-of is to use std::conditional and have SomeLargeClass when needed, and some dummy small class in other case;

struct Dummy
{
    // To be compatible with possible constructor of SomeLargeClass
    template <typename ...Ts> Dummy(Ts&&...) {} 
};

template <bool B> void foo()
{
    [[maybe_unused]] std::conditional_t<B, SomeLargeClass, Dummy> x;
    if constexpr(B) {
        // ... do something with x
    }
    // ... do something without x
    if constexpr(B) {
        // ... do something more with x
    }
}
  1. Yes, compilers can optimize unused variables, supposed it can proove that construction and destruction has no observable side effects.

  2. It is not SFINAE, because not a type x; makes the whole function fail. There is no alternative foo, hence it is a hard error.

  3. Yes, you can specialize foo:

.

struct SomeLargeClass {};

template <bool T> void foo();

template <> void foo<false>() {
    //... do something without x
}

template <> void foo<true>() {
    SomeLargeClass x;
    //... do something with x
    foo<false>();
    //... do something more with x
}

You could use the local variable x, but give it a specialized type:

#include <iostream>

using std::ostream;

template <bool T> struct MaybeLargeType;
template <> struct MaybeLargeType<true> { int bigone; };
template <> struct MaybeLargeType<false> {};

ostream& operator<<(ostream& s, const MaybeLargeType<true>& o) { return s << o.bigone; }
ostream& operator<<(ostream& s, const MaybeLargeType<false>& o) { return s << "nope"; }

template <bool T> void foo() {
  MaybeLargeType<T> x;
  if constexpr(T) {
    x.bigone = 1;
  }
  // other stuff
  if constexpr(T) {
    x.bigone += 3;
  }
  std::cout << x;
}

int main()
{
foo<true>();
foo<false>();
return 0;
}

This moves the LargeType inside variable x, which is big-or-small depending on the template parameter, so your code in the if constexpr blocks is slightly more wordy.

Just a variant of the specialisation approach:

template <bool B>
class C
{
public:
    void step1() { };
    void step2() { };
};

template <>
class C<true>
{
public:
    void step1() { /* use the large data*/ };
    void step2() { /* use the large data*/ };
private:
    // large data
};

template <bool B>
void foo()
{
    C<B> x;
    x.step1();
    // x-unaware code
    x.step2();
}

Which one looks better? Just a pure matter of taste...

I'll leave finding better names to you.

If your class has a trivial constructor, just don't worry - the compiler will not allocate an unused object on stack.

If your class has a constructor which does some work, you might want to skip this work if you know it's wasted. The compiler might still notice that the object is unused, and skip the constructor. Check this before you do any changes to your code (premature optimization)!

But if the constructor has some side-effects (not recommended), you have to help the compiler. One way to do it is by using unique_ptr:

template <bool T> void foo()
{
    unique_ptr<SomeLargeClass> x;
    if constexpr(T)
    {
        ... allocate x
        ... do something with *x
    }
    
    ... do something without x
    
    if constexpr(T)
    {
        ... do something more with *x
    }
}
Related