模板类的模板成员的模板专业化

时间:2014-04-29 10:43:46

标签: c++ templates c++11 specialization

这可能只是一个语法问题。

所以我有这个模板类:

template <typename String, template<class> class Allocator>
class basic_data_object
{
  template<typename T>
  using array_container = std::vector<T, Allocator<T>>;
};

还有一个:

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};

现在,我希望将第二个T参数与第一个内部typedef array_container一起用于任何给定类型。

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::template array_container<T>>
{
};

但是当我将std :: vector作为最后一个参数传递时,这种专业化似乎并不匹配。

如果我创建一个临时的硬编码typedef:

typedef basic_data_object<std::string, std::allocator<std::string>> data_object;

并将其用于专业化,一切正常:

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};

我错过了什么? :)


或者,最好(最小/最干净)的方法是什么?

5 个答案:

答案 0 :(得分:8)

C ++标准在[temp.class.spec.match]第2段中说:

  

部分特化与给定的实际模板匹配    参数列表是否为partial的模板参数    专业化可以从实际模板中推断出来    参数列表(14.8.2)。

14.8.2是[temp.arg.deduct],即描述函数模板的模板参数推导的子句。

如果您修改代码以使用类似的函数模板并尝试调用它,您将看到无法推断出参数:

template <typename String, typename T>
void deduction_test(String,
                    typename basic_data_object<String, std::allocator>::template array_container<T>)
{ }

int main()
{
  deduction_test(std::string{}, std::vector<int, std::allocator<int>>{});
}

(我删除了Allocator参数,因为无法将模板模板参数作为函数参数传递,而在basic_data_object类型中,它是非推断的上下文,我不相信它会影响结果。)

clang和GCC都说他们不能在这里推断T。因此,部分特化与用作模板参数的相同类型不匹配。

所以我还没有真正回答这个问题,只是澄清了原因在于模板参数推导的规则,并且在函数模板中显示了与推导的等价。

在14.8.2.5 [temp.deduct.type]中,我们得到一个阻止扣除的非推断上下文列表,以及第6段中的以下规则:

  

如果以包含非推断上下文的方式指定类型名称,则包含该类型名称的所有类型也不会被推断。

由于basic_data_object<String, Allocator>位于非推导的上下文中(它是嵌套名称说明符,即出现在::之前),这意味着类型{{1}也是非推断的,这正是Clang和GCC告诉我们的。


使用临时硬编码的typedef,没有非推断的上下文,因此使用T函数模板对T的扣除成功:

deduction_test

因此,相应地,当您使用该类型时,您的类模板部分特化可以匹配。


在没有更改template <typename String, typename T> void deduction_test(String, typename data_object::template array_container<T>) { } int main() { deduction_test(std::string{}, std::vector<int, std::allocator<int>>{}); // OK } 的定义的情况下,我没有找到使其正常工作的方法,但如果这是一个选项,则可以删除推断get_data_object_value类型的必要性而是使用特征来检测类型是否是您想要的类型,并专注于特征的结果:

array_container

如果您需要多个类模板部分特化,那么这并不是真正可扩展的,因为您需要使用默认参数添加多个#include <string> #include <vector> #include <iostream> template <typename String, template<class> class Allocator> class basic_data_object { public: template<typename T> using array_container = std::vector<T, Allocator<T>>; template<typename T> struct is_ac : std::false_type { }; template<typename T> struct is_ac<array_container<T>> : std::true_type { }; }; template <typename String, template<class> class Allocator, typename T, bool = basic_data_object<String, Allocator>::template is_ac<T>::value> struct get_data_object_value { }; template <typename String, template<class> class Allocator, typename T> struct get_data_object_value<String, Allocator, T, true> { void f() { } }; int main() { get_data_object_value<std::string,std::allocator,std::vector<short>> obj; obj.f(); } 模板参数。

答案 1 :(得分:6)

出于某种原因,问题似乎源于双层模板。我将让你检查下面的3个测试用例,它们很简单:

  1. 删除First的模板参数:按预期工作
  2. 使First成为模板,但内部类型为普通模板:按预期工作
  3. 同时制作First和内部类型模板:编译,但输出是意外的
  4. 注意:模板模板参数Allocator无法重现问题,因此我将其删除了。

    注意:GCC(ideone的版本,4.8.1我相信)和Clang(Coliru版本,3.4)编译代码,但产生相同的令人困惑的结果

    从上面的3个例子中,我推断出:

    • 这不是一个不可导出的背景问题;否则为什么(2)会工作?
    • 这不是别名问题;否则为什么(1)会工作?

    因此,问题比目前的提示更加毛茸茸会使我们相信或者gcc和Clang都有错误。

    编辑:感谢Jonathan Wakely耐心地教育我,我终于理解了与此案相关的标准措辞及其应用方式。我现在试着用我自己的话来解释这个问题。请参阅Jonathan的答案,了解确切的标准报价(全部位于 [temp.deduct.type]

    • 在推断模板参数(P i )时,无论是函数还是类,对每个参数都是独立完成的。
    • 每个参数需要为每个参数提供零个或一个候选 C i ;如果一个参数提供了多个候选,则它不提供任何参与。
    • 因此,每个参数产生字典D n :P i - &gt; C i ,它将要推导出的模板参数的子集(可能为空)映射到其候选者。
    • 字典D n 合并在一起,参数由参数组成:
      • 如果只有一个字典具有给定参数的候选字符,则接受此参数,使用此候选字符
      • 如果多个词典对于给定参数具有相同的候选词,则接受此参数,并使用此候选词
      • 如果多个词典对于给定参数具有不同的不兼容(*)候选,则此参数将被拒绝
    • 如果最终字典已完成(将每个参数映射到接受的候选人),则扣除成功,否则失败

    (*)似乎有可能从可用的候选人中找到“普通类型”......但这并不重要。

    现在我们可以将它应用于前面的例子:

    1)存在单个模板参数T

    • 模式匹配std::vector<int>typename First::template ArrayType<T>std::vector<T>),我们得到D 0 { T -> int }
    • 合并唯一字典会产生{ T -> int },因此T推断为int

    2)存在单个模板参数String

    • 模式匹配std::vector<int>针对String,我们得到D 0 { String -> std::vector<int> }
    • 模式匹配std::vector<int>针对typename First<String>::ArrayType我们遇到了不可导入的上下文(String的许多值都适合),我们得到D 1 :{{ 1}}
    • 合并两个词典会产生{},因此{ String -> std::vector<int> }推断为String

    3)存在两个模板参数std::vector<int>String

    • 模式匹配T针对std::vector<char>,我们得到D 0 String
    • 模式匹配{ String -> std::vector<char> }针对std::vector<int>我们点击了不可导入的上下文,我们得到D 1 typename First<String>::template ArrayType<T>
    • 合并两个词典会产生{},这是一个不完整的词典({ String -> std::vector<char> }不存在)扣除失败

    我必须承认我还没有考虑过这些论点是彼此独立解决的,因此在最后一种情况下,当计算D 1 时,编译器无法利用这样的事实: D 0 已经推导出T的值。然而,为什么以这种方式完成它可能是一个完整的问题。


    Without the outer template, it works,就像它打印“Specialized”:

    String

    Without the inner template, it works,就像它打印“Specialized”:

    #include <iostream>
    #include <vector>
    
    struct First {
        template <typename T>
        using ArrayType = std::vector<T>;
    };
    
    template <typename T>
    struct Second {
        void go() { std::cout << "General\n"; }
    };
    
    template <typename T>
    struct Second < typename First::template ArrayType<T> > {
        void go() { std::cout << "Specialized\n"; }
    };
    
    int main() {
        Second < std::vector<int> > second;
        second.go();
        return 0;
    }
    

    With both, it fails,就像打印“General”一样:

    #include <iostream>
    #include <vector>
    
    template <typename String>
    struct First {
        using ArrayType = std::vector<int>;
    };
    
    template <typename String, typename T>
    struct Second {
        void go() { std::cout << "General\n"; }
    };
    
    template <typename String>
    struct Second < String, typename First<String>::ArrayType > {
        void go() { std::cout << "Specialized\n"; }
    };
    
    
    int main() {
        Second < std::vector<int>, std::vector<int> > second;
        second.go();
        return 0;
    }
    

答案 2 :(得分:5)

Jonathan Wakely的回答说明了你的代码不起作用的原因。

我的回答告诉你如何解决问题。


在您的示例中,您要专门化的容器类型是在basic_data_object之外定义的,因此您当然可以直接在专业化中使用它:

template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,std::vector<T,A>>
{ };

这绝对符合标准,适用于所有编译器。


如果在basic_data_object中定义了类型,则可以将其移出课程。

示例:而不是

template<typename S, template<class> class A>
struct a_data_object
{
    template<typename T>
    struct a_container
    { };
};

写下这个:

template<typename S, template<class> class A, typename T>
// you can perhaps drop S and A if not needed...
struct a_container
{ };

template<typename S, template<class> class A, typename T>
struct a_data_object
{
    // use a_container<S,A,T>
};

现在你可以专攻:

template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };

注意:下一个“解决方案”显然是GCC 4.8.1的错误。

如果容器仅在封闭模板中定义且无法移出,则可以执行以下操作:

  1. basic_data_object获取容器类型:

    template<typename S, template<class> class A, typename T>
    using bdo_container = basic_data_object<S,A>::array_container<T>;
    
  2. 为此类型编写专门化:

    template <typename S, template<class> class A, typename T>
    struct get_data_object_value<S,A,bdo_container<S,A,T>>
    { };
    

答案 3 :(得分:2)

  

或者,最好(最小/最干净)的方法是什么?

可以说是:

  • 写一个确定Tr<String,Allocator,T>是否为T的SFINAE特质模板basic_data_object<String,Allocator>::array_container<T::E>E相同 对于某些类型T::value_type - ,如果有 - 即get_data_object_value
  • 为模板Tr<String,Allocator,T>::value提供第4个参数 默认为get_data_object_value
  • 编写实例化true的部分特化 第4个参数分别为false#include <type_traits> #include <vector> #include <iostream> template <typename String, template<class> class Allocator> struct basic_data_object { template<typename T> using array_container = std::vector<T, Allocator<T>>; }; template<typename T, typename String, template<class> class Allocator> struct is_basic_data_object_array_container /* A trait template that has a `static const bool` member `value` equal to `true` if and only if parameter type `T` is a container type with `value_type E` s.t. `T` = `basic_data_object<String,Allocator>::array_container<T::E>` */ { template<typename A> static constexpr bool test(std::is_same< A, typename basic_data_object<String,Allocator>::template array_container<typename A::value_type> > *) { return std::is_same< A, typename basic_data_object<String,Allocator>::template array_container<typename A::value_type> >::value; } template<typename A> static constexpr bool test(...) { return false; } static const bool value = test<T>(nullptr); }; template < typename String, template<class> class Allocator, typename T, bool Select = is_basic_data_object_array_container<T,String,Allocator>::value > struct get_data_object_value; template < typename String, template<class> class Allocator, typename T > struct get_data_object_value< String, Allocator, T, false > { static void demo() { std::cout << "Is NOT a basic_data_object array_container" << std::endl; } }; template < typename String, template<class> class Allocator, typename T> struct get_data_object_value< String, Allocator, T, true > { static void demo() { std::cout << "Is a basic_data_object array_container" << std::endl; } }; #include <list> #include <memory> using namespace std; int main(int argc, char **argv) { get_data_object_value<string,allocator,std::vector<short>>::demo(); get_data_object_value<string,allocator,std::list<short>>::demo(); get_data_object_value<string,allocator,short>::demo(); return 0; }

这是一个演示程序:

Is a basic_data_object array_container
Is NOT a basic_data_object array_container
Is NOT a basic_data_object array_container

使用gcc 4.8.2构建,clang 3.4。输出:

constexpr

由于缺少template<typename T, typename String, template<class> class Allocator> struct is_basic_data_object_array_container { template<typename A> static auto test( std::is_same< A, typename basic_data_object<String, Allocator>::template array_container<typename A::value_type> > * ) -> std::integral_constant< bool, std::is_same< A, typename basic_data_object<String, Allocator>::template array_container<typename A::value_type> >::value >{} template<typename A> static std::false_type test(...); using type = decltype(test<T>(nullptr)); static const bool value = type::value; }; 支持,VC ++ 2013将无法对此进行编译。为了适应这种情况 编译器可以使用以下不太自然的特性实现:

{{1}}

答案 4 :(得分:1)

修改:此答案仅适用于GCC 4.8.1中的错误


如果您在专业化中删除关键字template,您的代码将按预期工作:

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
    void foo() { std::cout << "general" << std::endl; }
};

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
typename basic_data_object<String, Allocator>::array_container<T>>
//                                         ^^^^^^ no template!
{
    void foo() { std::cout << "special" << std::endl; }
};

使用GCC 4.8.1测试的示例:

int main()  {
    get_data_object_value<std::string,std::allocator,std::vector<int>> obj;
    obj.foo(); // prints "special"
}