C++:如何在模板函数中使用类型进行分支?

12

我对模板不是很熟练。如何编写一个名为“get”的模板函数,根据模板类型选择要获取的数组?请参见以下示例:

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 );

你编写的方式不可能实现,因为你的代码中没有任何“通用”的东西。你只是想要一个选择器来选择两个成员变量。你可以使用普通的类设计来实现。如果想要真正通用的东西,可以尝试使用 boost.variant。 - Kerrek SB
Kerrek:这只是一个简单的例子。在我的实际程序中,我有更多类型特定的数组。我不想为它们编写单独的函数。 - Ashwin Nanjappa
1
所有这些数组都必须成为类的成员吗?这可能会很棘手,因为类必须是明确的,但如果您只有有限数量的容器,我有一种感觉模板并不是解决这个问题的正确领域。 - Kerrek SB
1
考虑尝试对此进行改进: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> {}; - Chris Lutz
6个回答

11

虽然Jason提出的解决方案可以工作,但它远非惯用方法,并且更难维护,因为switch语句中的case值1)没有明显的含义(“魔法数字”);2)很容易与switch_value<>特化中的值不同步。我建议使用以下方法:

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];
    }
};

如果使用除了 intchar 以外的任何类型调用 Foo::get<>,将会产生编译错误。


9
你需要添加某种值结构,以便从中获取用于switch语句的值。例如:
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 ];
    }
}

这里的好处是,如果使用没有有效值的类型,则会抛出编译器错误,因为switch_value<T>的默认版本没有定义成员value。因此,如果您没有为特定类型专门定义该结构,则此类模板实例化将失败。


Jason:非常感谢!这个解决方案对我的问题非常完美。除非有人提供更优雅的解决方案,否则你的解决方案就是最佳的 :-) - Ashwin Nanjappa
编译器会优化掉那段代码吗?最终的代码不是以 switch 结束,而是以一个函数结束,该函数仅针对具有该模板的每个函数的情况。 - Fernando Barbosa Gomes

3
所有这些答案都太过复杂了。
template <typename T>
T get( int idx )
{
   if ( boost::is_same<T, int>::value)
      return *(T*)&iArr[ idx ];
   else
      return *(T*)&cArr[ idx ];
}

如果 T 可以是类类型,例如 std::list<>,那么这种方法将无法工作。 - ildjarn
is_same 也在C++标准库中。 - mallwright

1
您可以专门化成员函数:
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...
};

适用于msvc++ 2010。希望这可以帮到你。

Jason建议的switch特化也应该可以正常工作。


1
这不是合法的C++代码;MSVC允许它作为一种静默扩展,但任何其他编译器都会出错。 - ildjarn

1
这可能对于你的示例来说有点过度,但如果你真的只需要一次存储一个数组,那么你可以使用boost::variant类和一个访问者,例如:
#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   

GetVisitor类所暗示的变量限制是,变量的所有成员都必须实现:

T operator[](int)

因此,您还可以将std::vector和std::deque作为变量的潜在成员之一添加。


0

我猜这不仅是你想要的模板函数:

在一个.h文件中

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

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

有时候你会像这样使用它:

Foo<int> foo1;
Foo<char> foo2;
int i  = foo1.get( 2 );
char c = foo2.get( 4 );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接