模板类的模板成员函数

4

我有一个模板化的C++类,其中包含一个模板化的成员函数。这个成员函数的模板参数依赖于类的模板参数以一种特定的方式(请参见下面的代码)。

我对该类进行了两个不同值的模板参数实例化(而非特化)。一切都编译通过。但是,如果调用模板化的成员函数,则仅针对第一个实例化的对象进行调用,第二个则无法编译通过。

看起来,编译器没有为模板类的第二个实例化实例化模板成员函数。我使用"g++ filename.cpp"编译以下代码,获得以下错误:

filename.cpp:63: error: no matching function for call to 'Manager<(Base)1u>::init(Combination<(Base)1u, (Dependent2)0u>*)’

这是调用b.init(&combination_2)的行。

g++ --version => g++ (Ubuntu/Linaro 4.4.7-1ubuntu2) 4.4.7

uname -a => Linux 3.2.0-25-generic-pae #40-Ubuntu SMP i686 i686 i386 GNU/Linux

enum Base {
  AA,
  BB,
  CC
};

enum Dependent1 {
  PP,
  QQ,
  RR
};

enum Dependent2 {
  XX,
  YY,
  ZZ
};

template<Base B>
struct DependentProperty {
};

template<>
struct DependentProperty<AA> {
  typedef Dependent1 Dependent;
};

template<>
struct DependentProperty<BB> {
  typedef Dependent2 Dependent;
};

template <Base B, typename DependentProperty<B>::Dependent D>
class Combination {
 public:
  void reset() {}
  int o;
};

template <Base B>
class Manager {
 public:
  template <typename DependentProperty<B>::Dependent D,
            template<Base,
                    typename DependentProperty<B>::Dependent> class T>
  void init(T<B, D>* t);
};

template <Base B>
template <typename DependentProperty<B>::Dependent D,
          template<Base,
                  typename DependentProperty<B>::Dependent> class T>
void Manager<B>::init(T<B, D>* t) {
  t->reset();
}

int main(int argc, char** argv) {
  Manager<AA> a;
  Manager<BB> b;
  Combination<AA, PP> combination_1;
  Combination<BB, XX> combination_2;
  a.init(&combination_1);
  b.init(&combination_2);
  return 0;
}

在我们实际的项目中,修改与我的示例代码中的Base、Dependent或Combination对应的类是不可行的。我真正想知道的是,我定义Manager::init()的语法是否有误,或者C++或g++是否存在某些已知的属性/特性/约束,不允许这段代码?


3
在定义模板函数init()之前使用typedef是明智的选择。typename DependentProperty<B>::Dependent重复了两次,这确实没有帮助理解。 - Stephane Rolland
我不是100%确定,但这可能是编译器的一个bug。 - BЈовић
4个回答

2
以下代码对我来说是可以编译的,我已经简化了你的代码,虽然它仍然实现了相同的功能。
template <Base B>
class Manager {
 public:
typedef typename DependentProperty<B>::Dependent D;  // if ever you need it
    template <typename TCombinaison>
    void init(TCombinaison* t)
    {
        t->reset();
    }

};

int main(int argc, char** argv) 
{
    typedef Combination<AA, PP> CombinaisonA;
    typedef Combination<BB, XX> CombinaisonB;

    typedef DependentProperty<AA> DependencyPropertyA;
    typedef DependentProperty<BB> DependencyPropertyB;

  CombinaisonA combination_1;
  CombinaisonB combination_2;

  Manager<AA> a;
  Manager<BB> b;

  a.init(&combination_1);
  b.init<&combination_2);

  return 0;
}

编辑: 根据评论区中的反馈,为了禁止在管理器中混合使用组合,这里提供第二个解决方案。现在我使用std::is_same来检查“概念”契约。

template <Base B, typename DependentProperty<B>::Dependent D>
class Combination {
 public:
    typedef typename DependentProperty<B>::Dependent DependencyType;
  void reset() {}
  int o;
};

template <Base B>
class Manager {
 public:
    typedef typename DependentProperty<B>::Dependent DependencyType; 
    template <typename TCombinaison>
    void init(TCombinaison* t)
    {
        static_assert(std::is_same<TCombinaison::DependencyType, Manager::DependencyType>);
        t->reset();
    }

};

它与我的预期代码不同,因为它允许编译b.init(&combination_1)。我希望init()仅接受那些与调用其init()的Manager对象共享相同Base模板参数的Combination对象。 - user2010172
1
@user2010172 我已经相应地修改了代码,使用 std::is_same 来检查 DependencyProperties 是否兼容。 - Stephane Rolland
使用std::is_same可以强制执行兼容性,但是与我的预期代码还有一个区别。在我的预期代码中,init()可以进一步使用Dependent模板参数(可能调用其他基于该参数的函数),但现在它不能这样做。另一个小问题是:现在我们要求每个TCombinaison候选者额外定义一个公共DependencyType,以便进行此std::is_same检查。 - user2010172
@user2010172,你得出结论有点太快了,说你不能再……实际上,在init()内部可以访问类型B和类型D的知识。显然,你的编译器以及我的(这里是VS2010)无法推断模板模板参数的类型。也许你应该尝试一些更明确的声明。 - Stephane Rolland
你说得对,在init()函数中获取那些信息是_可能的_,但我能否在不修改所有现有且满足init()模板参数T所要求的有效类的情况下获取它们呢? - user2010172
不要修改已经存在的 TCollection 候选项... 不... 你说得对。 - Stephane Rolland

0

你的代码是正确的,除了函数调用部分。

a.init<PP, Combination>( &combination_1 );
b.init<XX, Combination> ( &combination_2 );

这个编译并且运行得很顺利。


0
如果您将继承与常量模板参数分离,并扩展组合以提供有关其模板参数的信息,则可以使代码编译,同时考虑到您不希望出现这种情况:
b.init(&combination_1);

你正在努力间接地指定和修复你的Manager中init成员模板的Combination类型,尽管init模板将会推导它,因为它是函数的唯一参数,并且类型已经在main中定义。

你是否考虑直接使用Combination来进行init模板化?

这样,除了init()声明之外的所有内容都保持不变,你的代码将按照最初的意图编译:

class Base
{
};

class AA
:
    public Base
{
};

class BB
: 
    public Base
{
};

class Dependent1
{
};

class PP
:
    public Dependent1
{};

class Dependent2
{};

class XX
:
    public Dependent2
{};

template<class Base>
struct DependentProperty {
};

template<>
struct DependentProperty<AA> {
  typedef Dependent1 Dependent;
};

template<>
struct DependentProperty<BB> {
  typedef Dependent2 Dependent;
};

template <class Base> 
class Combination {
 public:

     typedef Base CombinationBase;
     typedef typename DependentProperty<Base>::Dependent CombinationDependent;

     void reset() 
     {

     }

     int o;
};


template <class Base>
class Manager
{
    public:

        // Any type C
        template<class C>
        void init (C* t)
        {
            // Any type C conforming to the implicit interface holding reset()
            t->reset(); 
            // Forcing specific combination
            Base b = typename C::CombinationBase(); 
            // Forcing it again
            typename DependentProperty<Base>::Dependent d = typename C::CombinationDependent();
        }
};

int main(int argc, char** argv) {

  Combination<AA> combination_1;
  Manager<AA> a;
  a.init(&combination_1);

  Manager<BB> b;
  Combination<BB> combination_2;
  b.init(&combination_2);

  b.init(&combination_1);

  return 0;
}

在这种情况下,您可以扩展组合模板以向客户端代码提供对其模板参数的访问。当然,在此情况下,模板C一旦依赖于其init成员函数中的实现(访问存储的模板参数值等),就会成为组合概念的细化。

这个建议确实是正确的,但我已经编辑了我的原始问题,以反映为什么我不能采取这种方法。 - user2010172
@user2010172 好的,我再看一下,但是第一个组合可以工作而第二个却不行,这真的很奇怪。 - tmaric

-1

我所看到的唯一事物是

template <typename DependentProperty<B>::Dependent D,
          template<Base, <-- wrong
                typename DependentProperty<B>::Dependent <-- wrong
          > class T>
void init(T<B, D>* t);

你的类Combination作为模板参数,但你想给它类型

我花了一些时间来修复它 - 就像这样

template <typename DependentProperty<B>::Dependent D,
          template<Base BB,
                typename DependentProperty<BB>::Dependent DD
          > class T>
void init(T<B, D>* t);

还有许多其他变体,但都没有成功。

抱歉将其排列为答案,但我无法在评论中输入如此多的代码。


template<Base>template<Base BB>是相同的。也许你想要使用的是template<typename Base> - Ben Voigt
@BenVoigt 你说得对。但是 typename DependentProperty<B>::Dependent 怎么样?模板会如何处理这个 typename - borisbn

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