这可能只是一个语法问题。
所以我有这个模板类:
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>>
{
};
我错过了什么? :)
或者,最好(最小/最干净)的方法是什么?
答案 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个测试用例,它们很简单:
First
的模板参数:按预期工作First
成为模板,但内部类型为普通模板:按预期工作First
和内部类型模板:编译,但输出是意外的 注意:模板模板参数Allocator
无法重现问题,因此我将其删除了。
注意:GCC(ideone的版本,4.8.1我相信)和Clang(Coliru版本,3.4)编译代码,但产生相同的令人困惑的结果
从上面的3个例子中,我推断出:
因此,问题比目前的提示更加毛茸茸会使我们相信或者gcc和Clang都有错误。
编辑:感谢Jonathan Wakely耐心地教育我,我终于理解了与此案相关的标准措辞及其应用方式。我现在试着用我自己的话来解释这个问题。请参阅Jonathan的答案,了解确切的标准报价(全部位于 [temp.deduct.type] )
(*)似乎有可能从可用的候选人中找到“普通类型”......但这并不重要。
现在我们可以将它应用于前面的例子:
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的错误。
如果容器仅在封闭模板中定义且无法移出,则可以执行以下操作:
从basic_data_object
获取容器类型:
template<typename S, template<class> class A, typename T>
using bdo_container = basic_data_object<S,A>::array_container<T>;
为此类型编写专门化:
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"
}