如何为模板类

时间:2015-08-21 16:07:23

标签: c++ templates inner-classes friend template-specialization

假设我有以下定义嵌套类的模板类:

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毛羽或专业化会影响其他(非预期​​)类型的风险。

想法?

3 个答案:

答案 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有两个原因:

  1. 有关maybeChangeType_adl的定义,有必要 构造一个返回类型的对象。
  2. 并非所有类型都可以从函数返回。例如。抽象类型,函数类型,数组类型。那些仍然可以用作 模板参数。
  3. 在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来专门化它。