9

您好我在选择具有明确专业化的模板类的正确版本时遇到问题。我想使用用于专业化的类的派生类来选择专业化。场景是:

#include <stdio.h>

class A
{};

class B: public A
{};

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

// Explicit specialization for A
template<> int Foo< A >::FooBar( void ) { return 20; }

void main( void)
{
   Foo<B> fooB;

   // This prints out 10 instead of wanted 20 ie compiler selects the general version
   printf("%d", fooB.FooBar() );
}

正如我在评论中所说,我希望看到 20 被打印出来,因为 B 是从 A 派生的,但 10 被打印出来。如何在为每个派生类编写专业化的情况下调用专业化(我的实际场景有很多派生类型)。

4

6 回答 6

7

---编辑:新答案让我们使原始方法更易于维护。所有重要的选择都可以在 Foo 的定义中找到。它应该易于维护。

#include <boost/mpl/if.hpp>
#include  <boost/type_traits/is_base_of.hpp>
#include <iostream>

class A
{};

class B: public A
{};

class C{};
class D : public C{};
class E{};

struct DefaultMethod
{
    static int fooBar() { return 10; }
};
struct Method1
{
    static int fooBar() { return 20; }
};
struct Method2
{
    static int fooBar() { return 30; }
};

template<typename T, typename BaseClass, typename Choice1, typename OtherChoice>
struct IfDerivesFrom :
    boost::mpl::if_<
        typename boost::is_base_of<BaseClass, T>::type,
        Choice1,
        OtherChoice>::type
{
};

template<typename T>
struct Foo :
    IfDerivesFrom<T, A,
      Method1,
      IfDerivesFrom<T, C,
          Method2,
          DefaultMethod>
      >
{
};

int main()
{
    std::cout << Foo<A>::fooBar() << std::endl;
    std::cout << Foo<B>::fooBar() << std::endl;
    std::cout << Foo<C>::fooBar() << std::endl;
    std::cout << Foo<D>::fooBar() << std::endl;
    std::cout << Foo<E>::fooBar() << std::endl;

    return 0;
}

---原始答案如果您可以使用boost,您可以执行以下操作:

#include  <boost/type_traits/is_base_of.hpp>

template<bool b>
class FooHelper
{
    int FooBar();
};
template<> FooHelper<true>::FooBar(){ return 20;}
template<> FooHelper<false>::FooBar(){ return 10;}

template<typename T>
class Foo
{
public:
   int FooBar(void) { return FooHelper<boost::is_base_of<A, T>::type::value>(); }
};
于 2010-01-22T17:07:03.943 回答
5

更一般地说,一般来说,模板和继承是一个长期存在的问题。

问题是模板在精确类型上工作并且不考虑继承因素,这两个概念有些正交,因此尝试混合一个和另一个通常容易出错。

您也可以使用以下方法进行检查:

template <class T>
int fooBar(T) { return 10; }

int fooBar(A) { return 20; }

B b;
fooBar(b); // this returns 10, because fooBar<T> is a better match (no conversion)

现在,关于你的问题,虽然我很欣赏已经给出的各种解决方案enable_ifis_base_of技巧,但我放弃了它们,因为它们不实用。专业化的重点是,Foo如果有必要,作者不必知道任何人将如何专业化她的课程,只是为了让它变得容易。否则,如果你需要十几个专业,你最终会得到一个非常非常奇怪Foo的课程,这是肯定的。

STL 已经处理过类似的问题。公认的习惯用法通常是提供一个特征类。默认的特征类为每个人提供了一个很好的解决方案,而人们可以专门化一个特征类来满足自己的需要。

我认为应该有一种使用概念的方法(即,如果 T 定义了 T::fooBar() 然后使用它,否则使用默认版本......),但对于特定的方法重载,这不是必需的。

namespace detail { int fooBar(...) { return 10; } }

template <class T>
class Foo
{
public:
  static int FooBar() { T* t(0); return ::detail::fooBar(t); }
};

现在,专门针对 A 的派生类:

namespace detail { int fooBar(A*) { return 20; } }

它是如何工作的?在考虑重载时,省略号是最后考虑的方法,因此之前符合条件的任何方法都可以,因此它非常适合默认行为。

一些考虑:

  • 命名空间:根据标识符fooBar是否可能被使用,您可能更愿意隔离到它自己(或专用于Foo类)的命名空间中,否则,进行非限定调用并让用户在命名空间中定义它她的课。

  • 这个技巧只适用于继承和方法调用,如果你想引入特殊的 typedef,它就不起作用

  • 您可以将更多模板传递给实际方法,例如真实类型

这是一个带有模板函数的示例

namespace detail { template <class T> int fooBar(...) { return 10; } }

template <class T>
int Foo<T>::FooBar() { T* t(0); return ::detail::fooBar<T>(t); }

namespace detail {
  template <class T>
  int fooBar(A*)
  {
    return T::FooBar();
  }
}

这里会发生什么:

struct None {};
struct A { static int FooBar() { return 20; } };
struct B: A {};
struct C: A { static int FooBar() { return 30; } };

int main(int argc, char* argv[])
{
  std::cout << Foo<None>::FooBar()  // prints 10
     << " " << Foo<A>::FooBar()     // prints 20
     << " " << Foo<B>::FooBar()     // prints 20
     << " " << Foo<C>::FooBar()     // prints 30
     << std::endl;
}
于 2010-01-22T17:58:28.703 回答
1

第一点(次要):您的标题不正确;这是显式专业化,而不是部分专业化。要获得部分特化,您需要指定至少一个模板参数,但至少要保留一个未指定的其他参数:

template <class T, class U>
demo { };

template <class T>
demo<int> {};  // use in the case of demo<XXX, int> 

查看您的代码,我对它完全可以编译感到有些惊讶。我不确定有什么方法可以强制调用您的专用函数。通常,您会将整个课程专门化:

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

template<>
class Foo<A> {
public:
    int FooBar() { return 20; }
};

在这种情况下,这对你没有任何好处。您可以将派生对象隐式转换为基础对象,但它仍然是一种转换。另一方面,模板的非专用版本可以在没有转换的情况下使用——并且在选择使用哪个模板时,编译器会将无需转换即可实例化的模板视为比需要隐式转换的模板更好的选择。转换。

于 2010-01-22T16:59:58.360 回答
1

这是一个解决方案,但它并不是特别好:

template<typename T>
class Foo
{
public:
  int FooBar(typename disable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 10; }
  int FooBar(typename enable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 20; }
};
于 2010-01-22T17:17:04.940 回答
0

您需要专注于确切的类型。例如Foo<A> fooA; fooA.FooBar();会给你20. 或者使用boost.type_traits如@Benoît 所示。

于 2010-01-22T16:59:44.440 回答
0

从根本上说,您希望在派生类上有一个模板特化触发器。

如果您不需要专门的实际类,只需要函数,那么您似乎可以这样做:

int foo::foobar(A &someA);

如果您实际上需要专门化该类,我认为您想查看接口和私有类数据模式;或多或少,接口将对象“简化”为模板专用识别类型,然后调用;翼

int foo::foobar(A &someA)
{ return fooImpl<A>::foobar(someA); }

但我想这并不能真正回答你的问题,因为它不支持一般情况。我想你可以有:

template<class T>
class foo
{
public: 
    int foobar(T &t);
    int foobar(A &a);
}

foo<A>::foobar(someA);
foo<F>::foobar(someA);
foo<not B>::foobar(someB); //Should trigger foobar(A &a), right?

这将能够识别 B 是从 A 派生的,同时仍然提供通用情况。我认为; 我没有测试过这个。

这不是最漂亮的,但我认为你有一些机会在那里获得一些有趣的访问控制之类的东西,如果你专门研究实际的课程,因为你可以包括或不包括各种 foobar(A &a) 之类的函数来允许或拒绝在各种继承树上使用;对于上面的例子;

foo<C>::foobar(someF); //doesn't exist!
于 2010-01-22T18:42:08.557 回答