views:

51

answers:

2

Hi,

One of my class' member method take as an argument of enumeration type: it produces different side effects for different enum. I was wondering whether it's possible to use template as a lookup table, two possible solutions came up to my mind, but none of them seems to work:

//// 1 ////

class A {

    public:

    enum AEnum : uint8_t { first, second, ... };

    private:

    template<AEnum N, typename T>
    struct impl {
        static void do_sth(T t) { ... };
    };

    template<typename T>
    struct impl<first, T> {
        static void do_sth(T t) { ... };
    };

    public:


    template<typename T>
    void do_sth(AEnum e, T t) {
        impl<e, T>::do_sth(t);
    }

}

//// 2 ////

class A {

    public:

    enum AEnum : uint8_t { first, second, ... };

    private:

    template<typename T_enum, typename T>
    struct impl {
        static void do_sth(T t) { ... };
    };

    template<typename T>
    struct impl<uint8_t[2], T> { // A::first
        static void do_sth(T t) { ... };
    };

    public:


    template<typename T>
    void do_sth(AEnum e, T t) {
        impl<uint8_t[static_cast<uint8_t>(e) + 1u], T>::do_sth(t);
    }

}

Is it really bad idea to code it this way?

@Oli Charlesworth

What's wrong with a switch statement?

Supported types of do_sth's second argument (T) varies with value of e, e.g. A::first supports integrals, and A::second STL containers, e.g.:

    template<typename T>
    void do_sth(AEnum e, T t) {
        switch(e) {
            case first:
                std::cout << &t << std::endl;
                break;
            case second:
                std::cout << t.data() << std::endl;
                break;
            default:
                break;
    }

A a;
a.do_sth(A::first, 0);
+1  A: 

Yes, what you've coded doesn't make any sense. Template instantiations are resolved at compiler-time, whereas obviously the value of e is only known at run-time.

What's wrong with a switch statement?

Oli Charlesworth
+3  A: 

You have to make the AEnum arg a template argument to do_sth:


 template<AEnum e, typename T>
    void do_sth(T t) { ... }

...and call it as a.do_sth<A::first>(0).

Alternatively, you could write separate functions (do_sth_integral, do_sth_container, ...), or, if there is only one correct course of action for a particular T, deduce the "correct" enum value for a given T using metaprogramming/overloading tricks.

For example, here's a way of writing two functions that e.g. detect numeric types and container types:


//The extra dummy argument is invalid for types without a nested 
//"iterator" typedef
template<typename T>
void do_sth(T t, typename T::iterator * = 0)
{
    //container type code
}

//The dummy arg is invalid for types without a 
//std::numeric_limits specialization
template<typename T>
void do_sth(T t, 
typename boost::enable_if_c<std::numeric_limits<T>::is_specialized>::type * = 0) 
{
    //numeric type code
}

Of course, this would fail if you passed a T which has an iterator typedef and a numeric_limits specialization, or has neither.

If there is only one sensible action for a particular T, and it's hard to correctly guess which overload should be used for an unknown T, then you could use a traits class that users have to specialize explicitly, or just require that users specialize an "impl" or dispatching class.

You can't write a function that does something like 3.data(), even if that code path is never called when the program runs. The compiler doesn't know that it will never be called, and in any case, it violates the type system of the language in a way that is required to cause a diagnosed error.

Doug