DCTLib
DCTLib

Reputation: 1056

Cleanest way for conditional code instantiation in C++ template

I'm trying to get the following C++ code running:

#include <cmath>

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};


template<bool hasdata> class A {
public:
    A() {
        ConditionalData<double,hasdata> data;
        if (hasdata) {
            data.setData(sin(cos(123.4)));
        }
    }
};


int main(int argNum, const char**argData) {
    A<false> test1;
    A<true> test2;
    return 0;
}

Essentially, I want to implement a templated class A in which certain operations are executed depending on the template parameter. These operations need local variables, which I only want to be allocated if needed. The problem that I'm having here is that the body of the

if (hasdata) {
    data.setData(3);
}

condition is also instantiated for hasdata=false, which does not compile (using g++ 5.2). Any ideas how to get this done in the cleanest way without splitting the body of A::A() into pieces?

The source code above is a minimal non-working example. The real implementation for A::A() is relatively long, with the parts dependent on "hasdata" being distributed evenly over the code. Also, the "typename T" for which the class A will be used is a relatively complex class with heavy-weight constructors/destructors, so I want the instances of T to only be allocated when hasdata=true. Finally, in the data.setData(...) calls, there can be complex computations in the "...", which should only be performed if needed.

Upvotes: 20

Views: 5197

Answers (6)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48467

If you can afford for , you can express the conditional branches as generic lambdas. The benefit is that they capture surrounding variables and the solution doesn't require extra member functions.

template <bool> struct tag {};

template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }

template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }

template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }

template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }

// ...

ConditionalData<int, hasdata> data;        
static_if<hasdata>([&](auto& d)
{
    d.setData(3);
})(data);

DEMO

In you can just say:

if constexpr (hasdata)
{
    data.setData(3);
}

DEMO 2

Upvotes: 15

TartanLlama
TartanLlama

Reputation: 65620

This is a common pattern, so there's actually a paper to add constexpr_if to C++. If that makes it in to future versions, it would allow you to keep your code pretty much as-is.

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        constexpr_if (hasdata) {
        //^^^^^^^^^^ instead of plain if
            data.setData(3);
        }
    }
};

For now, you'll need to make do with one of the other answers.


Edit: This was added to C++17 and called if constexpr

Upvotes: 7

Waterlimon
Waterlimon

Reputation: 139

You can use the preprocessor to 'generate' each variation of your class as template specializations.

First, the 'template' header we will generate the specializations from:

ATemplate.h
//no include guards to allow multiple inclusion
template<>
class A<A_HAS_DATA>
{
public:
    A()
    {
#if A_HAS_DATA
        double data;
        if (hasdata) {
            data = sin(cos(123.4));
        }
#endif
    }
}

Then we actually generate each specialization to obtain a normal header to use in your code:

A.h
#pragma once

template<bool hasdata>
class A;

//Generate specialization for hasdata = true
#define A_HAS_DATA 1
#include "ATemplate.h"
#undef A_HAS_DATA
//(undef avoids redefinition warning)
//Generate specialization for hasdata = false
#define A_HAS_DATA 0
#include "ATemplate.h"
#undef A_HAS_DATA

Essentially, instead of manually writing each specialization for each possible case (given that you might have multiple such settings to include/exclude stuff), we use preprocessor to generate each variant by including a header multiple times, each time with a different value for preprocessor define(s) to get different result.

Prefer to use normal template approaches when they work, but if the amount of manual code duplication (to define all the possible variants) grows too high, this approach can work (as you can specify everything 'inline' akin what static if would do if we had one)

Upvotes: 3

Jarod42
Jarod42

Reputation: 217468

If you cannot(/don't want to) change ConditionalData, you may create 2 methods instead:

template<typename T>
void SetData(ConditionalData<T, false>& , const T& ) {/* Nothing */}

template<typename T>
void SetData(ConditionalData<T, true>& c, const T& value) { c.setData(value); }

and then

A() {
    ConditionalData<int, hasdata> data;
    SetData(data, 3);
}

For more complex cases

template<typename T>
void A_impl_part1(ConditionalData<T, false>&) {/* Nothing */}

template<typename T>
void A_impl_part1(ConditionalData<T, true>& c) { c.setData(3); }

and then

A() {
    ConditionalData<int, hasdata> data;
    A_impl_part1(data);
    // common part
    // A_impl_part2(data); // and so on
}

Upvotes: 3

iammilind
iammilind

Reputation: 70000

First of all, you don't require 3 versions of class ConditionalData, because bool can be either true or false. So let me simplify it as following:

template<typename T, bool = false> class ConditionalData {
};                 //^^^^^^^^^^^^

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

Secondly, to answer your question: Whichever members are falling for false category, just overload them outside the class body as following:

template<bool hasdata> class A { 
public:
    A() {
        ConditionalData<int,hasdata> data;
        if (hasdata) {
            data.setData(3);
        }
    }   
};

template<> A<false>::A() {}  // Does nothing for `false` condition

Upvotes: 5

Andriy Tylychko
Andriy Tylychko

Reputation: 16266

you can define setData for both branches, empty one for false condition:

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
    void setData(T _data) {}
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        data.setData(3);
    }
};

Upvotes: 4

Related Questions