2

The X:

A common pattern I'm seeing is that the underlying code for a function is templates, but for "reasons" the template code is not available at the upper layer (pick from aversion to templates in interface, the need for a shared library and not to expose implementation to customer, reading type settings at run time instead of compile time, etc.).

This often makes the following:

struct foo { virtual void foo() = 0;}
template <typename T> struct bar : public foo
{
    bar( /* Could be lots here */);
    virtual void foo() { /* Something complicated, but type specific */}
};

And then an initialize call:

foo* make_foo(int typed_param, /* More parameters */)
{
    switch(typed_param)
    {
        case 1: return new bar<int>(/* More parameters */);
        case 2: return new bar<float>(/* More parameters */);
        case 3: return new bar<double>(/* More parameters */);
        case 4: return new bar<uint8_t>(/* More parameters */);
        default: return NULL;
    }
}

This is annoying, repetitive, and error prone code.

So I says to myself, self says I, there has GOT to be a better way.

The Y:

I made this. Do you all have a better way?

////////////////////////////////////
//////Code to reuse all over the place
///
template <typename T, T VAL>
struct value_container
{
    static constexpr T value() {return VAL;}
};

template <typename J, J VAL, typename... Ts>
struct type_value_pair
{
    static constexpr J value() {return VAL;}

    template <class FOO>
    static auto do_things(const FOO& foo)->decltype(foo.template do_things<Ts...>()) const
    {
        foo.template do_things<Ts...>();
    }
};

template <typename T>
struct error_select
{
    T operator()() const { throw std::out_of_range("no match");}
};

template <typename T>
struct default_select
{
    T operator()() const { return T();}
};

template <typename S, typename... selectors>
struct type_selector
{
    template <typename K, class FOO, typename NOMATCH, typename J=decltype(S::do_things(FOO()))>
    static constexpr J select(const K& val, const FOO& foo=FOO(), const NOMATCH& op=NOMATCH())
    {
        return S::value()==val ? S::do_things(foo) : type_selector<selectors...>::template select<K, FOO, NOMATCH, J>(val, foo, op);
    }
};

template <typename S>
struct type_selector<S>
{
    template <typename K, class FOO, typename NOMATCH, typename J>
    static constexpr J select(const K& val, const FOO& foo=FOO(), const NOMATCH& op=NOMATCH())
    {
        return S::value()==val ? S::do_things(foo) : op();
    }
};

////////////////////////////////////
////// Specific implementation code
class base{public: virtual void foo() = 0;};

template <typename x>
struct derived : public base
{
    virtual void foo() {std::cout << "Ima " << typeid(x).name() << std::endl;}
};


struct my_op
{
    template<typename T>
    base* do_things() const
    {
        base* ret = new derived<T>();
        ret->foo();
        return ret;
    }
};

int main(int argc, char** argv)
{
    while (true)
    {
        std::cout << "Press a,b, or c" << std::endl;
        char key;
        std::cin >> key;

        base* value = type_selector<
            type_value_pair<char, 'a', int>,
            type_value_pair<char, 'b', long int>,
            type_value_pair<char, 'c', double> >::select(key, my_op(), default_select<base*>());

        std::cout << (void*)value << std::endl;
    }

    /* I am putting this in here for reference. It does the same
       thing, but the old way: */

    /*
        switch(key)
        {
            case 'a':
              {
                  base* ret = new derived<int>();
                  ret->foo();
                  value = ret;
                  break;
              }

            case 'b':
              {
                  base* ret = new derived<char>();
                  ret->foo();
                  value = ret;
                  break;
              }

            case 'c':
              {
                  base* ret = new derived<double>();
                  ret->foo();
                  value = ret;
                  break;
              }

            default:
                return NULL;
        }
    */
}

Problems I see with my implementation:

  1. It is clear and readable as mud
  2. Template parameters MUST be types, have to wrap values in types (template <typename T, T VAL> struct value_container { static constexpr T value() {return VAL;} };)
  3. Currently no checking/forcing that the selectors are all type-value pairs.

And the only pros:

  1. Removes code duplication.
  2. If the case statement gets high/the contents of do_things gets high, then we can be a little shorter.

Has anyone do something similar or have a better way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • So, the biggest problem I see with your suggestion (well, ok, readability may be worse...) is that it only works for constant values of `typed_param`, since templates do not work with "runtime variables". (Edit: Unless the mud is so unclear, I don't understand how it works!) – Mats Petersson Apr 15 '14 at 19:07
  • @MatsPetersson I agree that it would be nice if there could be non-constant typed_params, but a switch-statement also requires "constant" typed_params for its case statements...in C++ the only way around that would elseifs. The "dynamic" part is only the input argument and whatever configurations of "foo" and "NOMATCH" are. edit: it also totally works, despite being ugly, so let me know if there is any specific questions on how it does! – IdeaHat Apr 15 '14 at 19:16
  • I'm a little unclear what you're trying to accomplish here? The make_foo function in your original example is easily replaced with a template function that gets rid of the redundant boilerplate but you say the client code can't be exposed to templates? Then your new code does a bunch of other stuff and ends up requiring the client code to use templates... Can you be a bit clearer about what you're trying to do? – mattnewport Apr 15 '14 at 20:06
  • 4
    If all you're trying to do is replace the `switch` statement then you can use a `std::map` where `case_type` is whatever type you were switching on and `factoryFunction` is a `std::function` that returns a value of type `foo*`. – YoungJohn Apr 15 '14 at 20:08
  • @YoungJohn hrm...and factory function would point towards a templated function, bar, to avoid the code duplication...that makes sense. Would make even more sense if there were templated lambdas. – IdeaHat Apr 16 '14 at 13:13
  • @YoungJohn Can you check that my answer below is in line with what you were thinking? I like the idea! – IdeaHat Apr 16 '14 at 13:50

3 Answers3

2

You can always walk a type list indexed by type_param, as in:

struct foo 
{
    virtual ~foo() = default;
    /* ... */
};

template<typename T>
struct bar : foo 
{ /* ... */ };


template<typename TL> 
struct foo_maker;

template<template<typename...> class TL, typename T, typename... Ts> 
struct foo_maker<TL<T, Ts...>>
{
    template<typename... Us>
    std::unique_ptr<foo> operator()(int i, Us&&... us) const
    {
        return i == 1 ?
            std::unique_ptr<foo>(new bar<T>(std::forward<Us>(us)...)) :
            foo_maker<TL<Ts...>>()(i - 1, std::forward<Us>(us)...); }
};

template<template<typename...> class TL> 
struct foo_maker<TL<>>
{
    template<typename... Us>
    std::unique_ptr<foo> operator()(int, Us&&...) const
    { return nullptr; }
};


template<typename...>
struct types;


template<typename... Us>
std::unique_ptr<foo> make_foo(int typed_param, Us&& us...)
{ return foo_maker<types<int, float, double, uint8_t>>()(typed_param, std::forward<Us>(us)...); };

Note: this factory function is O(n) (although a clever compiler could make it O(1)), while the switch statement version is O(1).

Nevin
  • 4,595
  • 18
  • 24
  • I'm going to say that switch statements are also O(1) iff there is a clever compiler that'll translate it into a jump statement. A valid approach: while more specialized to a single implementation than mine, that isn't necessarily a bad thing. – IdeaHat Apr 16 '14 at 16:32
  • (I'll still +1 as a creative solution, but I feel compelled to point out the functionality this method lacks). 1. It has the sames problems as my original post. 2. The typed_param MUST be an integral enumeration, else require a second lookup. 3. The /*more params*/ need to either be passed down the chain, it cannot be initialized into the parent (cannot do stuff in the constructor of foo_maker). 4. Cannot select collections of template initializes (what do you do if bar is `template struct bar`?). – IdeaHat Apr 16 '14 at 16:42
  • 3. Edited my code to forward parameters. 4. You can always walk two type lists, and is O(t + j). – Nevin Apr 16 '14 at 17:42
  • Valid points, but it still hangs on to the same problems that my implementation had. YoungJohn's solution (translated below) can support value template arguments, and it is really obvious what you are doing, at the cost of the `std::bind` copy pasta. – IdeaHat Apr 16 '14 at 18:38
  • Just had a thought, you could totally make this always O(1) by making the switch table yourself... – IdeaHat Apr 17 '14 at 13:16
1

Just to expand YoungJohn's comment, it looks like this (I've included a single initialization of the operator, and it could be made simpler if there was no parameters, but if there are no parameters there is little reason to do this anyway :-P).

#include <functional>
#include <map>


////////////////////////////////////
//////specific impmenetation code
class base{public: virtual void foo() = 0;};

template <typename x>
struct derived : public base
{
    virtual void foo() {std::cout << "Ima " << typeid(x).name() << std::endl;}
};

struct my_op
{
    int some_param_; /// <shared parameter

    my_op(int some_param) : some_param_(some_param){} /// <constructor

    template<typename T>
    base* do_stuff() const
    {
        std::cout << "Use some parameter: " << some_param_ << std::endl;
        base* ret = new derived<T>();
        ret->foo();
        return ret;
    }
};

base* init_from_params(int some_param, char key)
{
    my_op op(some_param);
    using factoryFunction = std::function<base*()>;
    std::map<char, factoryFunction> mp
    {
        { 'a', std::bind(&my_op::do_stuff<int>, &op)},
        { 'b', std::bind(&my_op::do_stuff<long int>, &op)},
        { 'c', std::bind(&my_op::do_stuff<double>, &op)}
    } ;
    factoryFunction& f = mp[key];
    if (f)
    {
        return f();
    }
    return NULL;
}


int main(int argc, char** argv)
{
    volatile int parameters = 10;

    while (true)
    {
        std::cout << "Press a, b, or c" << std::endl;
        char key;
        std::cin >> key;

        base* value = init_from_params(parameters, key);

        std::cout << (void*)value << std::endl;
    }
}

Pros: so much shorter, so much more standard, so much less weird template stuff. It also doesn't require the templated arguments to all be types, we can select whatever we want to initialize the function.

Cons: In theory, it could have more overhead. In practice, I totally doubt that the overhead would ever matter.

I like it!

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • This is more or less what I was getting at. It seems a shame to have to construct the map from scratch every time init_from_params is called though, but if the map isn't created there then there is a question of ownership. Should the map be a member of base? As things are currently written probably not. But yes, this is essentially the approach I was getting at. – YoungJohn Apr 16 '14 at 21:14
  • Ah, I see now, since the factory functions likely require parameters passed in you basically have to recreate the map each time. – YoungJohn Apr 16 '14 at 21:25
  • @YoungJohn Exactly. Making map a (static) member base could avoid the multiple of the map (you could re-assign the settings if they need to change) but would introduce threading issues. Something that would be totally doable is to make a factory class that holds all the settings AND the map (basically make `init_from_params` a member function of `my_op` and replace `op` in that call with `this`). That class would still not be thread safe (with respect to settings changes), but you could make only one per thread and you'd be good to go. For large number of key-values, unordered may be faster. – IdeaHat Apr 17 '14 at 13:14
-1
template<class T>
foo* make_foo(int typed_param,/*more params*/)
{
    return new bar<T>(/*more params*/);
}
Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
  • wrapping a template in a template directly only exposes another template. What we are looking for is something akin to mapping run-time variables to a template type, in order to select the template we will use at run time rather than compile time, and avoid code duplication. – IdeaHat Apr 16 '14 at 13:50
  • @MadScienceDreams you are kind of mixing chalk and cheese there – Neil Kirk Apr 16 '14 at 13:57
  • Not really? templates can be a powerful tool to avoid reimplementing the same code for multiple types. A stupidly simple example would be demosaicing a bayer patterned image. You can write one template and get 8 bit, 16 bit, and 32 bit code all with one implementation. Problem is, you don't know what camera you are going to use at compile time: your template covers all cases, but needs to be initialized at compile time to be used. You need to select the template you'll used based on some camera parameter at run time. And thus the switch statement. – IdeaHat Apr 16 '14 at 14:07