Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I simplify this "variable as template parameter" in C++?

How can I simplify this Code?

mfer::i_value* make_empty_value(mfer::tag tag_)
{
    if (tag_ == mfer::tag::mwf_ble) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_ble>());
    } else if (tag_ == mfer::tag::mwf_chn) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_chn>());
    } else if (tag_ == mfer::tag::mwf_blk) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_blk>());
    } else if (tag_ == mfer::tag::mwf_seq) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_seq>());
    } else if (tag_ == mfer::tag::mwf_man) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_man>());
    } else if (tag_ == mfer::tag::mwf_ivl) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_ivl>());
    } else if (tag_ == mfer::tag::mwf_sen) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_sen>());
    } else if (tag_ == mfer::tag::mwf_wfm) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_wfm>());
    } else if (tag_ == mfer::tag::mwf_pre) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pre>());
    } else if (tag_ == mfer::tag::mwf_off) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_off>());
    } else if (tag_ == mfer::tag::mwf_nul) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_nul>());
    } else if (tag_ == mfer::tag::mwf_pnt) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pnt>());
    } else if (tag_ == mfer::tag::mwf_nte) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_nte>());
    } else if (tag_ == mfer::tag::mwf_txc) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_txc>());
    } else if (tag_ == mfer::tag::mwf_flt) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_flt>());
    } else if (tag_ == mfer::tag::mwf_skw) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_skw>());
    } else if (tag_ == mfer::tag::mwf_mss) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_mss>());
    } else if (tag_ == mfer::tag::mwf_pnm) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pnm>());
    } else if (tag_ == mfer::tag::mwf_pid) {
        return memory_manager::instance().add(new mfer::t_value<mfer::tag::mwf_pid>());
    }

    return nullptr;
}

Briefly stating,

  • mfer::tag is enumeration, defined like enum tag {}; in namespace mfer.

  • mfer::i_value is abstract class.

    class i_value {};
    
  • mfer::t_value is templated class like,

    template <mfer::tag tag_type>
    class t_value : public i_value {};
    

At this moment, I don't know how to simplify this make_empty_value().

Ideally, I want to make it like this:

mfer::i_value* make_empty_value(mfer::tag tag_)
{
    return memory_manager::instance().add(new mfer::t_value<tag_>());
}

But I know that it is template, so above one doesn't make sense.

Is there any idea simplify this code? (Some modern C++ features, Boost libraries, and so on)

like image 598
Jafffy Avatar asked Jun 19 '16 17:06

Jafffy


People also ask

What is correct for template parameter?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

What is template type parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Why do we use template template parameter?

Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.

Can template parameters have default values?

Just like in case of the function arguments, template parameters can have their default values. All template parameters with a default value have to be declared at the end of the template parameter list.

What is a parametrized variable in C++?

Until variable templates were introduced in C++14, parametrized variables were typically implemented as either static data members of class templates or as constexpr function templates returning the desired values. Variable templates cannot be used as template template arguments .

What is a template parameter in C++?

An identifier that names a non-type template parameter of class type T denotes a static storage duration object of type const T, called a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template parameter.

How do I parameterize a template?

Every template is parameterized by one or more template parameters, indicated in the parameter-list of the template declaration syntax: template < parameter-list > declaration Each parameter in parameter-list may be: a non-type template parameter;

What are variable templates in C++17?

Interestingly, variable templates are used in C++17 to provide helpers for each type traits with values. For instance, std::is_same will have a std::is_same_v helper that is a variable template. With that, we can simplify our traits a bit more:


2 Answers

With a little template work, we can get the factory function down to:

i_value* make_empty_value(tag tag_type)
{
    static constexpr auto factory = make_factory(all_tags());

    auto index = std::size_t(tag_type - tag::first);
    if (index < tag::ntags) {
        return memory_manager::instance().add(factory[index]());
    }
    else {
        return nullptr;
    }
}

Full code below.

The i_value generator map is built at compile time, allowing constant-time lookup.

constraints:

  • the values in the enum must be consecutive, but they need not begin at zero.

  • this demo requires c++14. It can be easily adapted to work with c++11. For c++03 we'd want to reach out to boost mpl or boost_pp.

complete working example:

#include <array>
#include <utility>
#include <deque>
#include <iostream>

// minimal implementation of virtual base
class i_value {
public:
    virtual void prove() const = 0;
    virtual ~i_value() = default;
};

// tag enum - note that we have supplied some extra introspection information
// these could just as well be constexpr integers outside the enum
enum tag
{
    ble,
    chn,
    blk,
    seq,

    first = ble,                // first available tag
    last = seq,             // last available tag
    ntags = last-first      // number of tags
};

/// Function to offset an index sequence by the distance from
/// zero to the first available tag - in case the first tag is not zero
template<std::size_t...tags>
constexpr auto tag_offset(std::index_sequence<tags...>)
{
    return std::index_sequence<(tags + tag::first)...>();
}

/// Function to compute an index sequence of all valid tags
constexpr auto all_tags()
{
    return tag_offset(std::make_index_sequence<std::size_t(ntags)>());
}

/// Factory function to generate a derived class for a given tag
template <tag tag_type>
class t_value : public i_value {
    void prove() const override { void(std::cout << "I have tag " << tag_type << std::endl); }
    ~t_value() { void(std::cout << "tag " << tag_type << " destroyed" << std::endl); }
};

template<tag tag_type>
i_value* make_instance()
{
    return new t_value<tag_type>();
}


/// Function to generate a 'factory' - an array of factory functions, one for
/// each tag in the variadic template argument tags...
/// Note that the array is zero-based, the tags may not be. All we care about
/// here is the size of the list of tags (and their values)
///
template<std::size_t...tags>
constexpr auto make_factory(std::index_sequence<tags...>)
{
    return std::array<i_value* (*)(), sizeof...(tags)>
    {
        &make_instance<static_cast<tag>(tags)>...
    };
}

// minimal memory manager
struct memory_manager {
    struct impl {
        i_value* add(i_value* item) {
            _ivalues.push_back(item);
            return item;
        };
        ~impl() {
            for (auto i = _ivalues.rbegin() ; i != _ivalues.rend() ; ++i) {
                delete *i;
            }
        }
        std::deque<i_value*> _ivalues;
    };
    static impl& instance()
    {
        static impl _instance = {};
        return _instance;
    }
};

// here is resulting factory function.
i_value* make_empty_value(tag tag_type)
{
    static constexpr auto factory = make_factory(all_tags());

    auto index = std::size_t(tag_type - tag::first);
    if (index < tag::ntags) {
        return memory_manager::instance().add(factory[index]());
    }
    else {
        return nullptr;
    }
}

// test
int main()
{
    for(auto tag_type : { tag::ble, tag::chn })
    {
        auto pvalue = make_empty_value(tag_type);
        pvalue->prove();
    }
}

expected output:

I have tag 0
I have tag 1
tag 1 destroyed
tag 0 destroyed
like image 81
Richard Hodges Avatar answered Oct 07 '22 10:10

Richard Hodges


You can map the tags to a factory method;

typedef std::unordered_map<mfer::tag,std::function<mfer::i_value*()>> TagMap;

TagMap create_tag_map()
{
    TagMap map;

    map[mfer::tag::mwf_ble] = [](){ return new mfer::t_value<mfer::tag::mwf_ble>(); };
    map[mfer::tag::mwf_chn] = [](){ return new mfer::t_value<mfer::tag::mwf_chn>(); };
    map[mfer::tag::mwf_blk] = [](){ return new mfer::t_value<mfer::tag::mwf_blk>(); };
    //...

    return map;
}

The create_empty_value method could then look like this:

mfer::i_value* make_empty_value(mfer::tag tag_)
{
    static TagMap factory = create_tag_map();

    auto it = factory.find( tag_ );      
    if( it != factory.end() )
    {
        return  memory_manager::instance().add( it->second() );
    }

    return nullptr;
}

see simplified version Live on Coliru

like image 26
Thomas Avatar answered Oct 07 '22 10:10

Thomas