C++: How to use type in template function to branch?
Asked Answered
P

6

12

I am not quite proficient with templates. How do I write the a template function called get that chooses the array it gets from based on the template type? See the example below:

struct Foo
{
    int iArr[10];
    char cArr[10];

    // How to pick array here based on template type?
    template < typename T >
    T get( int idx )
    {
        // This does NOT work!
        switch ( T )
        {
        case int:
            return iArr[ idx ];
        case char:
            return cArr[ idx ];
        }
    }
};

// Expected behaviour of get()
Foo foo;
int i  = foo.get< int >( 2 );
char c = foo.get< char >( 4 );
Pohl answered 23/6, 2011 at 2:17 Comment(4)
The way you wrote it that's not possible, since there's nothing "generic" about your code. You just want a selector for two members. You can just use ordinary class design for that. For something truly generic you could try boost.variant.Roue
Kerrek: This is just a simple example. In my actual program, I have many more type specific arrays. I do not want to write separate functions for all of them.Pohl
Do all those arrays have to be members of your class? That would be tricky because classes must be definite, but if you only have a bounded amount of containers, I have a feeling that templates aren't really the right domain for the problem here.Roue
Consider tinkering with this: template <typename T> struct helper { T arr[10]; T get(std::size_t i) { return arr[i]; } }; struct foo : public helper<int>, public helper<char> {};Colum
B
11

While the solution proposed by Jason works, it's far from idiomatic, and is harder to maintain since the case values in the switch statement 1) have no apparent meaning ("magic numbers") and 2) could easily get out of sync with the values in the switch_value<> specializations. I would propose this instead:

struct Foo {
    Foo() : iArr(), cArr() { }

    template<typename T>
    T get(std::size_t const idx) const     {
        return Foo::get_dispatcher<T>::impl(*this, idx);
    }

private:
    int iArr[10];
    char cArr[10];

    template<typename T>
    struct get_dispatcher;
};

template<>
struct Foo::get_dispatcher<int> {
    static int impl(Foo const& foo, std::size_t const idx) {
        return foo.iArr[idx];
    }
};

template<>
struct Foo::get_dispatcher<char> {
    static char impl(Foo const& foo, std::size_t const idx) {
        return foo.cArr[idx];
    }
};

Invoking Foo::get<> with any type other than int or char will yield a compiler error.

Bullivant answered 23/6, 2011 at 3:10 Comment(0)
T
9

You would need to add a value structure of some type you can use to get the values for your switch-statement from. For instance:

template<typename T>
struct switch_value {};

template<>
struct switch_value<int>
{
    enum { value = 1 };
};

template<>
struct switch_value<char>
{
    enum { value = 2 };
};

//then inside you structure Foo
template <typename T>
T get( int idx )
{
    switch ( switch_value<T>::value )
    {
    case 1:
        return iArr[ idx ];
    case 2:
        return cArr[ idx ];
    }
}

The nice thing here is this should throw a compiler error if you use a type that does not have a valid value since the default version of switch_value<T> does not define a member value, so if you haven't specialized the structure for a specific type, then such a template instantiation will fail.

Tini answered 23/6, 2011 at 2:27 Comment(2)
Jason: Thanks so much! This works perfectly for my problem. Unless someone gives a more elegant solution, yours is the solution :-)Pohl
Would the compiler optimize that code away, instead of ending with a switch in the final code, ending with a function that does only the case for each generation of functions with that template ?Cassycast
W
4

All of these answers are wayyyy overkill.

template <typename T>
T get( int idx )
{
   if ( boost::is_same<T, int>::value)
      return *(T*)&iArr[ idx ];
   else
      return *(T*)&cArr[ idx ];
}
Wilone answered 30/7, 2013 at 18:23 Comment(2)
This won't work if T can be a class type, e.g. std::list<>.Bullivant
is_same is also in the C++ standard libraryNicolanicolai
D
1

You could specialise the member function:

struct foo
{
    int  iArr[10];
    char cArr[10];

    template<typename T>
    T &get(int ipos) { assert( false && "Foo.get: Invalid type!" ); return T(); }

    template<>
    int &get<int>(int ipos) { return iArr[ipos]; }

    template<> 
    char &get<char>(int ipos) {return cArr[ipos]; }

    // add more specialisations for any other types...
};

Works with msvc++ 2010. Hope this helps.

The switch specialisation suggested by Jason should also work fine.

Dreamy answered 23/6, 2011 at 2:42 Comment(1)
This is not legal C++; MSVC allows it as a silent extension, but any other compiler will choke.Bullivant
G
1

This is probably overkill for your example, but if you really only need to store one array at a time, then you can use the boost::variant class and a visitor, e.g.,

#include <boost/variant.hpp>
#include <iostream>

template< typename T >
class GetVisitor : public boost::static_visitor<T>
{
  public:
    GetVisitor(int index) : index_(index) {};

    template <typename U >
    T operator() (U const& vOperand) const
    {
        return vOperand[index_];
    }

  private:
    int index_;
};


int main ()
{
    int  iArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    char cArr[10] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' };

    boost::variant<int*, char*>  intVariant(iArr);   //- assign integer array to variant
    boost::variant<int*, char*>  charVariant(cArr);  //- assign character array to another variant

    int  testInt  = boost::apply_visitor(GetVisitor<int>(2),  intVariant);  
    char testChar = boost::apply_visitor(GetVisitor<char>(9), charVariant);

    std::cout << "returned integer is "   << testInt  << std::endl;
    std::cout << "returned character is " << testChar << std::endl;

    return 0;
}

output is:   
returned integer is 3   
returned character is j   

The restriction on the variant implied by the GetVisitor class is that all members of the variant must implement:

T operator[](int)

So you could also add, e.g., std::vector and std::deque as potential members of the variant.

Gabby answered 23/6, 2011 at 12:2 Comment(0)
R
0

I assume this is what you want beside just focusing on template function:

in a .h file

template < typename T >
struct Foo
{
    T arr[10];

    T get( int idx )
    {
      return arr[ idx ];
    }
};

somewhere you use it like:

Foo<int> foo1;
Foo<char> foo2;
int i  = foo1.get( 2 );
char c = foo2.get( 4 );
Rajiv answered 30/7, 2013 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.