Template specialization on template member of template class

Dra*_*rax 19 c++ templates specialization c++11

This is probably only a syntax problem.

So i have this template class :

template <typename String, template<class> class Allocator>
class basic_data_object
{
  template<typename T>
  using array_container = std::vector<T, Allocator<T>>;
};
Run Code Online (Sandbox Code Playgroud)

And another one :

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
{
};
Run Code Online (Sandbox Code Playgroud)

Now i want to specialize the second one's T parameter with the first one's inner typedef array_container for any given type.

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

But this specialization doesn't seem to be matched when i pass an std::vector as the last parameter.

If i create a temporary hard coded typedef:

typedef basic_data_object<std::string, std::allocator<std::string>> data_object;
Run Code Online (Sandbox Code Playgroud)

And use it for the specialization, everything works :

template <typename String, template<class> class Allocator, typename T>
struct get_data_object_value
<String, Allocator,
data_object::template array_container<T>>
{
};
Run Code Online (Sandbox Code Playgroud)

What did i miss ? :)


Alternatively what is the best (smallest / cleanest) way to make this work ?

Jon*_*ely 8

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

如果可以从实际模板参数列表推导出部分特化的模板参数,则部分特化匹配给定的实际模板参数列表(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>>{});
}
Run Code Online (Sandbox Code Playgroud)

(我删除了Allocator参数,因为没有办法将模板模板参数作为函数参数传递,并且在basic_data_object类型中它是一个非推导的上下文,我不相信它会影响结果.)

铿锵和海湾合作委员会都表示他们不能T在这里演绎.因此,部分特化与用作模板参数的相同类型不匹配.

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

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

如果以包含非推导上下文的方式指定类型名称,则包含该类型名称的所有类型也是非推断的.

由于basic_data_object<String, Allocator>处于非推导的上下文(它是嵌套名称说明符,即之前出现::),这意味着该类型T也是非推导的,这正是Clang和GCC告诉我们的.


使用临时硬编码的typedef,没有非推断的上下文,因此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
}
Run Code Online (Sandbox Code Playgroud)

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


我没有看到一种方法可以在不改变定义的情况下使其工作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();
}
Run Code Online (Sandbox Code Playgroud)

如果您需要多个类模板部分特化,则无法真正扩展,因为您需要bool使用默认参数添加多个模板参数.


Mat*_* M. 6

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

  1. 删除模板参数First:按预期工作
  2. 制作First一个模板,但内部类型是普通的:按预期工作
  3. 制作两者First和内部类型模板:编译但输出是意外的

注意:模板模板参数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 - > 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:{}
  • 合并两个字典的产量{ String -> std::vector<int> },因此String被推断为std::vector<int>

3)两个模板参数StringT存在

  • 模式匹配std::vector<char>反对String,我们得到d 0:{ String -> std::vector<char> }
  • 模式匹配std::vector<int>typename First<String>::template ArrayType<T>我们打非抵扣范围内,我们得到d 1:{}
  • 合并两个字典的产量{ String -> std::vector<char> },这是一个不完整的字典(T缺席),演绎失败

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


没有外部模板,它可以工作,就像打印"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;
}
Run Code Online (Sandbox Code Playgroud)

没有内部模板,它可以正常工作,因为它打印"Specialized":

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

两者都失败了,就像打印"General"一样:

#include <iostream>
#include <vector>

template <typename String>
struct First {
    template <typename T>
    using ArrayType = std::vector<T>;
};

template <typename String, typename T>
struct Second {
    void go() { std::cout << "General\n"; }
};

template <typename String, typename T>
struct Second < String, typename First<String>::template ArrayType<T> > {
    void go() { std::cout << "Specialized\n"; }
};

int main() {
    Second < std::vector<char>, std::vector<int> > second;
    second.go();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*vil 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>>
{ };
Run Code Online (Sandbox Code Playgroud)

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


在定义类型的情况下basic_data_object,您可以将其移出类.

示例:而不是

template<typename S, template<class> class A>
struct a_data_object
{
    template<typename T>
    struct a_container
    { };
};
Run Code Online (Sandbox Code Playgroud)

写这个:

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

现在你可以专注于:

template <typename S, template<class> class A, typename T>
struct get_data_object_value<S,A,a_container<S,A,T>>
{ };
Run Code Online (Sandbox Code Playgroud)

注意:下一个"解决方案"显然是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>;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 为此类型编写专门化:

    template <typename S, template<class> class A, typename T>
    struct get_data_object_value<S,A,bdo_container<S,A,T>>
    { };
    
    Run Code Online (Sandbox Code Playgroud)