根据派生类型选择类的显式特化

use*_*168 7 c++ templates explicit-specialization

嗨,我在选择具有明确特化的模板化类的正确版本时遇到问题.我想要使​​用用于专门化的类的派生类来选择特化.场景是:

#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() );
}
Run Code Online (Sandbox Code Playgroud)

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

Ben*_*oît 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;
}
Run Code Online (Sandbox Code Playgroud)

---原始答案如果您可以使用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>(); }
};
Run Code Online (Sandbox Code Playgroud)


Mat*_* M. 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)
Run Code Online (Sandbox Code Playgroud)

现在,关于你的问题,虽然我很欣赏使用enable_ifis_base_of欺骗的各种解决方案,但我认为它们不实用.专业化的一点是作者Foo不必知道如果有必要,任何人都会专门研究她的课程,只是为了让它变得简单.否则,如果你需要十几个专业,你最终会得到一个非常奇怪的Foo课程,这是肯定的.

STL已经处理过类似的问题.接受的习语通常是提供特质课程.默认traits类为每个人提供了一个很好的解决方案,同时可以专门设置一个traits类来满足一个人的需要.

我认为应该有一种方法使用Concepts(即,如果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); }
};
Run Code Online (Sandbox Code Playgroud)

而现在,专门为A的派生类:

namespace detail { int fooBar(A*) { return 20; } }
Run Code Online (Sandbox Code Playgroud)

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

一些考虑:

  • 命名空间:取决于标识符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();
  }
}
Run Code Online (Sandbox Code Playgroud)

在这里会发生什么:

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;
}
Run Code Online (Sandbox Code Playgroud)