Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?
like image 711
user2904251 Avatar asked Dec 07 '21 09:12

user2904251


Video Answer


4 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
    }
}
like image 144
Jarod42 Avatar answered Nov 15 '22 00:11

Jarod42


  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
}
like image 42
463035818_is_not_a_number Avatar answered Nov 14 '22 23:11

463035818_is_not_a_number


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.

like image 22
Adriaan de Groot Avatar answered Nov 14 '22 23:11

Adriaan de Groot


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.

like image 43
Aconcagua Avatar answered Nov 14 '22 23:11

Aconcagua