Cleanest way for conditional code instantiation in C++ template

后端 未结 6 1556
深忆病人
深忆病人 2021-02-18 19:11

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

#include 

template class ConditionalData {
};

template 

        
相关标签:
6条回答
  • 2021-02-18 19:27

    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);
        }
    };
    
    0 讨论(0)
  • 2021-02-18 19:27

    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
    }
    
    0 讨论(0)
  • 2021-02-18 19:27

    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)

    0 讨论(0)
  • 2021-02-18 19:32

    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

    0 讨论(0)
  • 2021-02-18 19:32

    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
    
    0 讨论(0)
  • 2021-02-18 19:40

    If you can afford for c++14, 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 c++17 you can just say:

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

    DEMO 2

    0 讨论(0)
提交回复
热议问题