6

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?
  • 3
    What does your compiler actually generate? Unused variables are likely to be optimized away, unless they are non-trivial types. – paddy Dec 07 '21 at 09:23
  • Remember that in C++, there is the [as-if](https://en.cppreference.com/w/cpp/language/as_if) rule. The code that you write is only a description of what you want done -- the final code produced by the compiler may not look anything like the source code, just as long as the program produces the correct results. This is all due to optimizations. Declaring variables that are not used -- a good compiler will optimize those variables away. – PaulMcKenzie Dec 07 '21 at 09:30
  • 2
    as a last resort you can always specialize `foo` (and refactor anything that does not need the optional variable to a seperate function to avoid duplication) – 463035818_is_not_an_ai Dec 07 '21 at 09:32
  • What do you mean by persistence? – Passer By Dec 07 '21 at 09:47
  • @PasserBy I mean I need the variable anywhere in the function scope. It shall not be restricted to a sub-scope like the if clauses. – user2904251 Dec 07 '21 at 10:31
  • as-if rule sounds ensuring. Thanks everyone. – user2904251 Dec 07 '21 at 10:33

5 Answers5

5

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
    }
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
4
  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
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Premise: I upvoted cause I think this approach is clearer than other answers. Can I just ask why we should go for something like this and not using the old good two distinct functions (foo_true and foo_false) avoiding templates? – Federico Dec 07 '21 at 10:14
  • @Federico the premise of the question is that `foo` is a template. If you want to select between `foo_true` and `foo_false` at compile time then the most simple way is to use a template `foo` – 463035818_is_not_an_ai Dec 07 '21 at 10:18
  • Thanks for the suggestion that is given in 3. As it makes sense in the simplified example that I have given, it is less desired in the actual usage. The flow of the code is so intertwined. This is some old code. – user2904251 Dec 07 '21 at 10:23
  • On the second item, actually the function won't fail since I only use the variable in the correct constexpr if block. But I got it now. I assume the compiler doesn't check if it fails or not. In general it may fail... Thanks alot. This was really helpful. – user2904251 Dec 07 '21 at 10:25
  • @user2904251 the code being old is no good argument to let it rot even more. Go and refactor it ;) – 463035818_is_not_an_ai Dec 07 '21 at 10:29
  • Old code with `if constexpr` which is C++17!? – Jarod42 Dec 07 '21 at 12:57
  • 1
    Suggesting to call `foo` from `foo` to avoid code duplication... – Aconcagua Dec 07 '21 at 14:03
1

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.

1

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.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
0

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
    }
}
anatolyg
  • 26,506
  • 9
  • 60
  • 134