C++,2参数类模板的部分特化:无法将函数定义与现有声明匹配

Posted

技术标签:

【中文标题】C++,2参数类模板的部分特化:无法将函数定义与现有声明匹配【英文标题】:C++, partial specialization of 2-argument class template: unable to match function definition to an existing declaration 【发布时间】:2016-07-14 12:51:16 【问题描述】:

我目前正在实现一个数据集帮助器类模板,该模板将浮点值 (Scalar) 存储在一个动态大小的 Eigen::Matrix 中,该模板由不同值类型的向量 (Element) 构成,另外还存储对此输入向量的引用.现在我想部分专门化向量值类型中的构造函数,保留标量类型中的模板以显式实例化。

不幸的是,我在 VS 2010 上遇到“无法将函数定义与现有声明匹配”。代码很简单:

template <class Scalar, class Element> struct DataSet

    DataSet(std::vector<Element> const & source);

    // several generic member functions here ...

    Eigen::Matrix<Scalar, ...  > data;
    std::vector<Element> const & source;
;

template<class Scalar>
DataSet<Scalar, SomeClass>::DataSet(std::vector<SomeClass> const & input)

    // special impl for Element==SomeClass ...

SomeClass 应该由编译器自动计算出来,如果正确完成但我尝试了所有有意义的组合但仍然得到:

*.cpp(79) C2244 : unable to match function definition to an existing declaration
see declaration of 'DataSet<Scalar, Element>::DataSet'

我还没有通过搜索互联网找到匹配的示例。提前致谢!

编辑

为了更具体,在我的真实案例中,我希望能够为 Element 的不同类型的构造函数定义几个部分特化,例如:

template<Scalar>
DataSet<Scalar, FirstClass>::DataSet(std::vector<FirstClass> const & first)
: data()
, source(first)

    // special impl here ...


template<Scalar>
DataSet<Scalar, std::shared_ptr<SecondClass> >::DataSet(std::vector<std::shared_ptr<SecondClass> > const & second)
: data()
, source(second)

    // special impl here ...

不希望将类完全重新声明/专门化为某个类型名。那么作为一个模板就没有什么用了。我想要解决方案,否则我的问题可能有其他策略。

FIN

由于看起来无法通过仅特化构造函数(这与类的隐式特化有关)来在类模板和构造函数之间共享类型 Element,因此我从类中删除了引用 source完全模板并将所需信息复制到通用容器中,并通过重载实现构造函数。

【问题讨论】:

【参考方案1】:

在定义构造函数时,您没有为它的类显式提供两个模板参数。这需要修改如下:

template<typename T_Scalar, typename T_Element>
DataSet<T_Scalar, T_Element>                    // template args for type
::DataSet(std::vector<T_Element> const &input)  // but none for constructor

    // stuff

切向相关:与方法不同,template arguments for classes cannot be deduced from constructor calls。那就是:until C++17 comes around!哇!

您面临的下一个绊脚石是模板特化不会“继承”其主模板的成员。假设他们会这样做有点直观,但事实并非如此。在我找到官方理由之前,我认为这是因为模板参数可能会使某些成员完全不适用于专业化,从而导致隐含的“继承”有问题。如果是这样,将决定要求完全重新声明/不值得添加神秘的语法来指定哪些主要的“基础”成员是“继承的”......当您可以简单地使用 real 继承时以确保它们是。

无论如何,这意味着要获得部分特化,您需要先声明整个事物——在这种情况下,是类及其构造函数——然后才能特化构造函数的定义。你没有提前声明这些,所以编译器正确地抱怨它看不到声明。

// Define specialised class
template<typename T_Scalar>
class DataSet<T_Scalar, SomeClass>

public:
    // Declare its ctor
    DataSet(std::vector<SomeClass> const &);


// Implement its ctor
template<typename T_Scalar>
DataSet<T_Scalar, SomeClass> // complete template args
::DataSet(std::vector<SomeClass> const &input)

    // stuff

See my working example of an equivalent template class, showing general vs. specialised instantiations.

增加你原来的困惑,这是公平的! - 请注意,如果模板类本身包含模板函数,则行外定义确实会变得非常复杂,因为您需要 2 template 子句,例如

template<typename TA, typename TB>
class Widget 
    template<typename TC>
    void accept_gadget(TC &&gadget);
;

template<typename TA, typename TB>
template<typename TC>
Widget<TA, TB>
::accept_gadget(TC &&gadget)

    /* ... */

如果the proposal to allow namespace class 在未来的版本中被接受,那么在许多情况下会有很大帮助,尤其是包括这种离线模板定义。很遗憾,它没有进入 C++17 ......而且很奇怪,它曾经丢失在第一名!

【讨论】:

好吧,我有点害怕,我期待 C++ 模板还不支持它(因为我没有找到匹配的例子)但我应该能够以某种方式使其工作,ain'我?稍后我很可能需要对输入向量的引用,这就是为什么 Element 应该保留类的模板参数,而不是构造函数......有什么建议吗? @daku 是的,它是一个模板参数,所以只需将它作为一个参数包含在内!查看我的编辑 使用Element=SomeClass 获得部分专业化的目的不是与一般形式不同吗? @O'Neil:是的,我很高兴克服了上面提到的编译器错误,但现在遇到了第二个专业化的问题,因为最初理解错误,给定的解决方案不是预期的专业化但再次使用 SomeClass 而不是 Element 作为任意模板参数名称的模板默认实现,我将尝试提供的解决方案,但我怀疑双模板 <...> 不正确,因为没有嵌套模板,请让我更新想法,我会在我终于成功时回复...... @daku 我已经为你调查过,认为你可以做到。唯一让我感到困惑的是,似乎我们必须在声明专业化时重新声明构造函数。请参阅答案中的新第二个示例以及我曾经在 Coliru 上解决此问题的等效演示。 :)【参考方案2】:

根据 §14.7.3.16:

在为类模板的成员或出现在命名空间范围内的成员模板的显式特化声明中,成员模板和它的一些封闭类模板可以保持非特化,除非声明不应显式特化类成员如果它的封闭类模板也没有明确专门化,则为模板。

不过,您仍然可以使用 std::enable_if 来部分专业化您的构造函数:

template <class Scalar, class Element> struct DataSet

    template <class T>
    DataSet(std::vector<T> const & input, std::enable_if_t<!std::is_same<T, SomeClass>> * = nullptr) 
        std::cout << "Element\n";
    
    template <class T>
    DataSet(std::vector<T> const & input, std::enable_if_t<std::is_same<T, SomeClass>> * = nullptr) 
        std::cout << "SomeClass\n";
    
;

但是这种方式是有限制的:

您的所有条件都必须是排他性的 您必须为要处理的每个新类修改类的代码。

相反,我建议您使用模板辅助结构:

DataSet(std::vector<Element> const & input) 
    Helper<Element>::do_it(input);

您可以根据需要进行专业化:

template <class Element>
struct Helper 
    static void do_it(std::vector<Element> const & input) 
        std::cout << "General form with Element\n";
    
;


template<>
struct Helper<SomeClass> 
    static void do_it(std::vector<SomeClass> const & input) 
        std::cout << "SomeClass\n";
    
;

template<>
struct Helper<SomeOtherClass> 
    static void do_it(std::vector<SomeOtherClass> const & input) 
        std::cout << "SomeOtherClass\n";
    
;

...

【讨论】:

感谢您的帮助,但这不会直接引用源数据并增加代码行数,这与我最初希望使其尽可能简单的愿望形成对比。我现在删除了对源数据的引用,并将信息抽象到某个通用容器中。构造函数现在可以在没有Element 的情况下实现为重载。但我仍然很好奇问题出在哪里,因为总的来说,从句法的角度来看,开始的方式应该是要走的路。 @daku 假设专用模板将从主要“基础”“继承”成员有点直观,但事实并非如此。我假定这是因为模板参数可能意味着某些成员在某些专业中完全不适用,从而使隐式“继承”成为问题。如果是这样,则必须已决定要求完全重新声明,并且认为不值得添加大量晦涩的语法来指定是否应“继承”成员...何时可以简单地使用 actual 继承以确保它们是。但我会看看我是否能找到一个记录在案的理由 @underscore_d:那是因为特化不是继承 ;-) 特化类模板会产生一个完整的新类。我的目标是专门化构造函数,这显然是不可能的,因为Element 是类模板的模板参数。如果我将构造函数设为模板函数,这应该很容易实现,但是可能无法在类和函数模板之间共享参数typename Element(对于ctor,还是以某种方式?)。 @daku 是的,专业化当然不是继承,因此我所有的“空中报价”:-P 据我了解,您的悲观情景是正确的:您无法模板化 ctor,因为您一无所有将它模板化on...假设您想重用已经模板化到类本身的类型。我不确定在没有完全重新声明或继承的情况下有没有办法解决这个问题——如果有的话,它肯定超出了我对模板杂技的有限掌握!我个人认为我会使用一些继承的混合来解决这个问题,但我没有使用这个确切的模式,所以我不能肯定 如有必要,您可以将 data 成员作为 do_it() 的参数传递。我已经编辑了我的答案,以向您展示您的期望。但是,如果您希望您的代码具有进化性,请考虑第二部分。

以上是关于C++,2参数类模板的部分特化:无法将函数定义与现有声明匹配的主要内容,如果未能解决你的问题,请参考以下文章

C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译

C++模板—部分特化

植物大战 模板——C++

C++模板进阶(非类型模板参数类模板的特化和模板的分离编译)

这是为成员函数的 C++ 模板部分特化构造类的好方法吗?

《c++从0到99》 六 模板