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

5

7

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 guarantees 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 */
        }
    }
    
  2. 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?

  3. Do I have any other options?

Surreptitious answered 7/12, 2021 at 9:21 Comment(6)
What does your compiler actually generate? Unused variables are likely to be optimized away, unless they are non-trivial types.Aga
Remember that in C++, there is the 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.Dalpe
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)Mycenae
What do you mean by persistence?Lookthrough
@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.Surreptitious
as-if rule sounds ensuring. Thanks everyone.Surreptitious
S
6

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
    }
}

As alternative, you might rewrite your function to have your class only in the constexpr block:

template <bool B> void foo()
{
    const auto do_something_without_x = [](){
        // ... do something without x
    };
    if constexpr(B) {
        SomeLargeClass x;
        // ... do something with x
        do_something_without_x();
        // ... do something more with x
    } else {
        do_something_without_x();
    }
}
Shirty answered 7/12, 2021 at 9:42 Comment(2)
Or just specialize Dummy on bool to be a holder of the "big" type.Summers
@alagner: AdriaandeGroot proposes similar solution. Naming seems more complicated though.Shirty
M
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
}
Mycenae answered 7/12, 2021 at 9:43 Comment(7)
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?Stannfield
@Stannfield 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 fooMycenae
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.Surreptitious
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.Surreptitious
@Surreptitious the code being old is no good argument to let it rot even more. Go and refactor it ;)Mycenae
Old code with if constexpr which is C++17!?Shirty
Suggesting to call foo<false> from foo<true> to avoid code duplication...Braze
A
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.

Azoth answered 7/12, 2021 at 9:44 Comment(0)
B
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.

Braze answered 7/12, 2021 at 14:17 Comment(0)
D
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
    }
}
Deathbed answered 7/12, 2021 at 9:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.