假设我有以下定义嵌套类的模板类:
template <typename T>
struct foo {
struct bar { };
};
假设我编写的环境也有以下帮助器类,它应该专门用于需要特殊处理的任何类型:
template <typename T>
struct maybeChangeType { using type = T; } /* default: same type */
如何为maybeChangeType
专门化foo<T>::bar
?例如,foo<int>::bar
可以很容易地专注,但foo
将与100多个不同的T
一起使用,因此这不是一个真正的选择。
注意:在将此问题标记为重复之前,请仔细阅读。这个问题是不询问如何专攻一般(例如Understanding templates in c++),或如何宣布朋友,甚至如何宣布模板的朋友。它询问如何为模板类的非模板嵌套成员声明朋友(如标题所述)。
尝试以“正常”方式定义专业化不起作用,因为foo<T>::bar
不是可推导的上下文(坏符号:前面需要typename
):
/* error: template parameters not deducible in partial specialization */
template <typename T>
struct maybeChangeType<typename foo<T>::bar>;
将专业化声明为朋友也会产生编译错误:
template <typename T>
struct foo {
struct bar {
/* errors:
* - class specialization must appear at namespace scope
* - class definition may not be declared a friend
*/
template <>
friend struct maybeChangeType<bar> { using type=T; };
};
};
以上错误清楚地表明这些朋友的实际定义必须脱节:
template <typename T>
struct foo {
struct bar {
friend struct maybeChangeType<bar>;
};
};
但现在我们又回到了我们开始的地方:任何为foo<T>::bar
定义专业化的尝试都会失败,因为它在不可导出的上下文中使用bar
。
注意:我可以通过提供朋友重载内联来解决函数问题,但这对于类没有帮助。
注意:我可以通过将内部类移到命名空间范围来解决这个问题,但这会显着地污染命名空间(用户真正没有业务的许多内部类)并使实现复杂化(例如,他们会不再能够访问其封闭类的私有成员,并且friend
声明的数量会激增)。
注意:我理解为什么允许对名称foo<T>::bar
进行任意专业化是危险/不合适的(例如foo<T>
有using bar = T
的情况),但在这种情况下{{ 1}}真的是一个类(甚至不是模板!),foo确实定义了,所以不应该有任何ODR毛羽或专业化会影响其他(非预期)类型的风险。
想法?
答案 0 :(得分:3)
作为一种侵入式解决方案,可以使用函数进行类型编程(元编程)。您可以将类型函数编写为友元函数:
template<typename T> struct type_t { using type = T; };
template <typename T>
struct foo {
struct bar {
friend constexpr auto maybeChangeType_adl(type_t<bar>) -> type_t<T>
{ return {}; }
};
};
将type_t<T>
替换为任何type_t<my_type_function_result>
。定义该功能不是必需的,但有时是方便的,例如,用于常量表达式中的计算。可以使用比较运算符增强type_t
,例如将std::is_same<A, B>
替换为中缀a == b
。直接使用type_t<T>
类型代替T
有两个原因:
maybeChangeType_adl
的定义,有必要
构造一个返回类型的对象。在C ++ 14中,我使用变量模板来实现这个功能:
template<typename T> constexpr auto type = type_t<T>{};
// ...
friend constexpr auto maybeChangeType_adl(type_t<bar>) { return type<T>; };
虽然这会失去一些对称性(参数vs返回类型)。
在任何情况下,您都可以按如下方式查询类型:
template<typename T> using inner_type = typename T::type;
template<typename T> using maybeChangeType =
inner_type<decltype(maybeChangeType_adl(type_t<T>{}))>;
可以提供回退函数maybeChangeType
来镜像OP中的主模板:
template<typename T> auto maybeChangeType(type_t<T>) -> type_t<T> { return {}; }
或者,您对maybeChangeType
函数的存在专门设置maybeChangeType_adl
类模板:
template<typename T, typename = void>
struct maybeChangeType { using type = T; };
template<typename T>
struct maybeChangeType<T, void_t<decltype(maybeChangeType_adl(type_t<T>{}))>>
{ using type = inner_type<decltype(maybeChangeType_adl(type_t<T>{}))>; };
答案 1 :(得分:1)
如果您可以在T
中导出模板参数bar
,并且可以向maybeChangeType
添加另一个(默认)模板参数,则可以尝试以下操作:
#include <type_traits>
template <typename T>
struct foo {
struct bar {
using type = T;
};
};
template <typename T, typename = void>
struct maybeChangeType { using type = T; }; /* default: same type */
template <typename T>
struct maybeChangeType<T,
typename std::enable_if<std::is_same<T,
typename foo<typename T::type>::bar
>::value
>::type>
{ using type = T; };
int main() {}
答案 2 :(得分:0)
受到@ ex-bart和@dyp的答案的启发,我能够提出以下C ++ 11解决方案:
struct Helper {
template <typename T>
static typename T::other_type getType(T);
template <typename T, typename... Ignored>
static T getType(T, Ignored...);
};
template <typename T>
struct maybeChangeType {
using type = decltype(Helper::getType(std::declval<T>()));
};
struct foo { };
struct bar { using other_type = int; }
int main() {
maybeChangeType<foo>::type f = foo();
maybeChangeType<bar>::type b = int();
}
它的优势在于不会将最终用户或类实现暴露给黑魔法 - 用户只需使用maybeChangeType
,类就可以通过简单地提供typedef other_type
来专门化它。