【问题标题】:How to define friends for classes defined inside template class如何为模板类中定义的类定义朋友
【发布时间】:2015-08-21 16:07:22
【问题描述】:

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

template <typename T>
struct foo {
    struct bar { };
};

假设我正在编码的环境也有以下帮助类,它应该专门用于需要特殊处理的任何类型:

template <typename T>
struct maybeChangeType { using type = T; }  /* default: same type */

如何将maybeChangeType 专门用于foo&lt;T&gt;::bar?专门针对 foo&lt;int&gt;::bar 很容易,但 foo 将与 100 多个不同的 T 一起使用,所以这不是一个真正的选择。

注意:在将此问题标记为重复问题之前,请仔细阅读。这个问题不是询问如何在一般情况下进行专业化(例如Understanding templates in c++),或者如何声明朋友,甚至如何声明模板的朋友。它询问如何为模板类的非模板嵌套成员声明朋友(如标题所述)。

试图以“正常”的方式定义特化是行不通的,因为foo&lt;T&gt;::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&lt;T&gt;::bar 定义特化的尝试都会失败,因为它在不可推导的上下文中使用了 bar

注意:我可以通过提供友元重载内联来解决函数问题,但这对类没有帮助。

注意:我可以通过将内部类移出命名空间范围来解决此问题,但这会严重污染命名空间(许多内部类用户实际上没有任何业务可玩)并使实现复杂化(例如,他们会不再可以访问其封闭类的私有成员,friend 声明的数量将激增)。

注意:我理解为什么允许任意特化名称 foo&lt;T&gt;::bar 会很危险/不受欢迎(例如,如果 foo&lt;T&gt;using bar = T 会怎样),但在这种情况下 bar 确实是一个类(甚至不是模板!) foo 确实定义了,因此不应该有任何 ODR 毛茸茸或专业化会影响其他(意外)类型的风险。

想法?

【问题讨论】:

  • 既然您已经知道如何解决过载问题,您是否想将您的问题简化为专业化问题?
  • template &lt;typename T, typename Enabler&gt; struct maybeChangeType 可能更简单...
  • @Barry 好主意,完成了。
  • @Jarod42 你能详细说明一下吗?
  • @dyp 向maybeChangeType() 抛出一个参数,看起来像一个答案?

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


【解决方案1】:

作为一种侵入式解决方案,可以使用函数进行类型编程(元编程)。您可以将类型函数编写为友元函数:

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&lt;T&gt; 替换为任何type_t&lt;my_type_function_result&gt;。没有必要,但有时很方便,定义这个函数,例如用于常量表达式中的计算。 type_t 可以通过比较运算符来增强,例如用中缀 a == b 替换 std::is_same&lt;A, B&gt;。直接使用 type_t&lt;T&gt; 类型而不是 T 有两个原因:

  1. 对于maybeChangeType_adl 的定义,需要 构造一个返回类型的对象。
  2. 并非所有类型都可以从函数返回。例如。抽象类型、函数类型、数组类型。那些仍然可以用作 模板参数。

在 C++14 中,我会使用变量模板来实现这个功能:

template<typename T> constexpr auto type = type_t<T>{};

// ...

friend constexpr auto maybeChangeType_adl(type_t<bar>) { return type<T>; };

虽然这失去了一些对称性(参数与返回类型)。


在任何情况下,您都可以按如下方式查询类型:

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>{}))>; };

【讨论】:

    【解决方案2】:

    如果可以在bar 中导出模板参数T,如果可以在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() {}
    

    【讨论】:

    • 上述示例中的using type = T 只是为了简洁。在现实生活中,它将是using type = AnotherNestedClass。不过,也许我可以使用 SFINAE 尝试将类型名从课程中拉出来......让我玩一下。
    • 如果您可以通过它们包含的某些成员类型识别bar 的实例,则可以使用AlwaysTrue&lt;typename T::member_type&gt;::value 作为enable_if 条件,其中template&lt;class&gt; struct AlwaysTrue : std::true_type {};
    • 但是我如何区分 enable_if 专用版本和默认版本,而不会让用户接触到这种恶心?
    • @ex-bart 我建议改用唯一类型标识符;类似struct foo_bar {}; 在命名空间范围内,然后在每个barusing identifier = foo_bar;,并查询enable_if_t&lt;is_same&lt;typename T::identifier, foo_bar&gt;::value&gt;
    • @Ryan 我尝试了一些变体here。所有人都可以使用 g++ 5.2.0(以及我的机器上的 g++ 4.9.2),除非我尝试将 AlwaysTrue 定义为类型别名而不是派生类。你用的是什么编译器?
    【解决方案3】:

    受到@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 来专门化它。

    【讨论】:

    • 这要求将支持的每种类型都放在一个位置——Helper——因此不能很好地扩展。基于 ADL 或 SFINAE 的解决方案可以在需要时分发支持。
    • @Yakk 我不认为我关注。如果T 包含类或typedef other_type,那么上面的内容会选择它。如果没有,将使用T。只要在使用 maybeChangeType 之前定义了 T ,它就应该可以工作。并且不可能对不完整的类型使用 MaybeChangeType,因为 std::declval 会爆炸。
    • 假设您有一个没有other_type 别名的template&lt;class T&gt; struct bob {};,并且如果T 是一个整数类型,您希望maybeChangeType 返回T。对于上述系统,任何此类改进都需要在struct Helper 内或通过修改maybeChangeType 来完成。您确实会得到“检测other_type 并在存在时使用它”,但仅此而已。还有更简洁的方法可以做到这一点!
    • 我一直对更简洁的做事方式很感兴趣,但您能详细说明一下吗?到目前为止,这里没有任何东西让我觉得是一个“干净”的解决方案。对于你描述的鲍勃情况,我只会专门化模板——不要乱七八糟,不要大惊小怪。适用于 std::array、std::pair 等。事实上,这就是我对所有代码所做的事情,直到我意识到我无法专门针对嵌套在模板类中的类......
    • can_apply 或等效的 std::experimental 版本。
    猜你喜欢
    • 2012-05-15
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-30
    相关资源
    最近更新 更多