C++ 如何为模板类中定义的类定义朋友

C++ 如何为模板类中定义的类定义朋友,c++,templates,inner-classes,friend,template-specialization,C++,Templates,Inner Classes,Friend,Template Specialization,假设我有以下定义嵌套类的模板类: template <typename T> struct foo { struct bar { }; }; 将专门化声明为友元也会产生编译错误: template <typename T> struct foo { struct bar { /* errors: * - class specialization must appear at namespace scope

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

template <typename T>
struct foo {
    struct 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; };
    };
};
模板
结构foo{
结构条{
/*错误:
*-类专门化必须出现在命名空间范围中
*-类定义不能声明为友元
*/
模板
友元结构maybeChangeType{using type=T;};
};
};
上述错误清楚地表明,这些朋友的实际定义必须是不一致的:

template <typename T>
struct foo {
    struct bar {
        friend struct maybeChangeType<bar>;
    };
};
模板
结构foo{
结构条{
friend结构可以改变类型;
};
};
但现在我们回到了起点:任何为
foo::bar
定义专门化的尝试都将失败,因为它在不可推断的上下文中使用
bar

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

注意:我可以通过将内部类移出名称空间范围来解决这个问题,但这会严重污染名称空间(用户实际上不需要处理许多内部类),并使实现复杂化(例如,他们将不再能够访问其封闭类的私有成员,并且
友人
声明的数量将激增)

注意:我理解为什么允许任意专门化名称
foo::bar
(如果
foo
使用bar=T
,会有危险/不可取之处),但在这种情况下
bar
实际上是一个类(甚至不是模板!)哪一个foo真正定义了,所以不应该有任何ODR毛茸茸的地方,也不应该有专门化会影响其他(意外)类型的风险


想法?

如果您可以在
栏中导出模板参数
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() {}
#包括
模板
结构foo{
结构条{
使用类型=T;
};
};
模板
结构maybeChangeType{using type=T;};/*默认值:相同类型*/
模板
结构maybeChangeType
{使用type=T;};
int main(){}

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

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 {}; }
    };
};
template<typename T> constexpr auto type = type_t<T>{};

// ...

friend constexpr auto maybeChangeType_adl(type_t<bar>) { return type<T>; };
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>{}))>; };
尽管这会失去一些对称性(参数与返回类型)


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

template<typename T> using inner_type = typename T::type;

template<typename T> using maybeChangeType =
    inner_type<decltype(maybeChangeType_adl(type_t<T>{}))>;
或者,在存在
maybeChangeType\u adl
函数的情况下,专门化
maybeChangeType
类模板:

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 {}; }
    };
};
template<typename T> constexpr auto type = type_t<T>{};

// ...

friend constexpr auto maybeChangeType_adl(type_t<bar>) { return type<T>; };
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>{}))>; };
模板
结构maybeChangeType{using type=T;};
模板
结构maybeChangeType
{使用类型=内部类型;};

受@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();
}
struct-Helper{
模板
静态类型名T::其他类型getType(T);
模板
静态T getType(T,忽略…);
};
模板
结构maybeChangeType{
使用type=decltype(Helper::getType(std::declval());
};
结构foo{};
结构条{使用其他类型=int;}
int main(){
maybeChangeType::type f=foo();
maybeChangeType::type b=int();
}

它的优点是不会将最终用户或类实现暴露于黑魔法之下——用户只需使用
maybeChangeType
,类只需提供一个typedef
other_type

就可以对其进行专门化,既然您已经知道如何解决过载问题,您想将问题简化为专门化吗操作问题?使用
模板结构maybeChangeType可能更简单。@Barry好主意,完成了。@Jarod42您能详细说明一下吗?@dyp在
maybeChangeType()中加入一个参数
看起来像是一个答案吗?我上面的例子中使用type=T
只是为了简洁。在现实生活中,使用type=AnotherNestedClass
可能是
的。也许我可以使用SFINAE尝试将typename从类中拉出来……让我来玩一下。如果你能通过某个成员类型识别出
bar
它们包含,您可以使用
AlwaysTrue::value
作为
enable_if
条件,其中
模板结构AlwaysTrue:std::true_类型{}
。但是我如何区分enable_if专用版本和默认版本,而不让用户暴露在这种讨厌的环境中呢?@ex bart我建议使用一个唯一的类型标识符;类似于
struct foo_bar{}
在命名空间范围内,然后在每个
栏中使用identifier=foo\u bar;
,并查询
enable\u if\u t
@Ryan我尝试了一些变体。所有这些都使用了g++5.2.0(以及我的机器上的g++4.9.2),除非我试图将
AlwaysTrue
定义为类型别名而不是派生类。您使用的编译器是什么?这要求将支持的每个类型都放在一个位置--
Helper
--因此无法很好地扩展。基于ADL或SFINAE的解决方案可以在需要时分发支持。@Yakk我不认为我可以下面。如果
T
包含类或typedef
other_type
,则上面的代码将拾取该类或typedef
other_type
。如果不包含,则将使用
T
。只要在使用maybeChangeType之前定义了T,它就应该可以工作。而且,由于std::declval爆炸,因此不可能使用不完整的maybeChangeType。假设您有一个
>模板结构bob{}
没有
其他类型的别名,如果
T
是一个整数类型,您希望
maybeChangeType
返回
T
。使用上述系统,任何此类改进都需要在
struct Helper
内完成,或者通过修改
maybeChangeType
来完成。您确实可以“检测
其他类型的
,如果有,就使用它”,但这是关于i