10

可能以前被问过,但这一切都接近我对 C++ 的理解和认知的极限,所以我在理解正在谈论的内容以及到底发生了什么方面有点慢。让我直接跳到代码。这有效:

template <typename T>
class Foo
{
    struct Bar
    {
        Bar() {}
        ~Bar() noexcept {}
        Bar(Bar&& b) : Bar() { swap(*this, b); }

        friend void swap(Bar& b1, Bar& b2) { /* ... */ }
    };
};

template class Foo<int>; // explicit instantiation of Foo with int type

但是如何将定义移到结构体swap之外呢?Bar如果我这样做:

template <typename T>
class Foo {
    struct Bar {
        // ...
        Bar(Bar&& b) : Bar() { swap(*this, b); } // line 16
        // ...
        template <typename V>
          friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

template class Foo<int>; // line 31

g++(4.7.1,标志:-Wall -std=c++11)报告:

main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&) 
            [with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:31:16:   required from here
main.cpp:16:28: error: no matching function for call to 
            ‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:16:28: note: candidate is:
main.cpp:26:6: note: template<class T> void swap(typename Foo<T>::Bar&, 
                                                 typename Foo<T>::Bar&)
main.cpp:26:6: note:   template argument deduction/substitution failed:
main.cpp:16:28: note:   couldn't deduce template parameter ‘T’

我想swap在显式实例化时也需要创建代码Foo,这是有道理的,但是为什么编译器无法确定swap(Foo<int>::Bar&...)需要创建的代码呢?为什么模板替换失败?还是我把一切都搞错了?

更新 1

和:

template <typename T> class Foo;
template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2);

template <typename T>
class Foo {
    struct Bar {
      Bar(Bar&& b) : Bar() { swap(*this, b); }  // line 19
      friend void swap<>(Foo<T>::Bar& b1, Foo<T>::Bar& b2); // line 20
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

template class Foo<int>; // line 29

g++(4.7.1,标志:-Wall -std=c++11)报告:

main.cpp: In instantiation of ‘struct Foo<int>::Bar’:
main.cpp:29:16:   required from here
main.cpp:20:17: error: template-id ‘swap<>’ for ‘void swap(Foo<int>::Bar&, Foo<int>::Bar&)’ does not match any template declaration
main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:29:16:   required from here
main.cpp:19:24: error: no matching function for call to ‘Foo<int>::Bar::Bar()’
main.cpp:19:24: note: candidate is:
main.cpp:19:5: note: Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]
main.cpp:19:5: note:   candidate expects 1 argument, 0 provided
main.cpp:19:28: error: no matching function for call to ‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:19:28: note: candidate is:
main.cpp:26:8: note: template<class T> void swap(typename Foo<T>::Bar&, typename Foo<T>::Bar&)
main.cpp:26:8: note:   template argument deduction/substitution failed:
main.cpp:19:28: note:   couldn't deduce template parameter ‘T’

更新 2

好的,所以不能这样做。Piotr已链接到Output a nested class inside a template,但我不明白答案。为什么不能swap在其声明之外定义?据我(错误)理解,为什么编译器不能为它创建代码swap(Foo<int>::Bar&...)并在代码中链接到它以显式实例化Foo<int>?我完全误解了发生了什么吗?有什么问题?

更新 3

好的,这不能完成,因为如果存在模板特化,编译器不能保证对swap定义外部的调用Foo是明确的,因为Foo<some_class>::Bar在特定的特化中可能是完全不同的东西。我希望我做对了。但是,为什么在我创建Foo?

template <typename T>
class Foo {
    struct Bar {
        // ...
        Bar(Bar&& b) : Bar() { swap(*this, b); }
        // ...
        template <typename V>
          friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
  void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {}

//template class Foo<int>; // let's comment this explicit instantiation out.

此代码编译良好(g++ 4.7.1,标志:-Wall -std=c++11)。但是,它不应该警告我这段代码可能会导致问题吗?当我添加 的显式实例化时Foo,问题不在于该行本身,而在于swapFoo.

4

2 回答 2

3

问题不在于friend.

问题在于这个函数本身:

template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26

T无法从嵌套类中推断模板参数,请参阅:在模板中输出嵌套类,或者更好的答案:https ://stackoverflow.com/a/4092248/1463922

举例说明为什么它不能完成,考虑这个函数:

template <class T>
void foo(typename A<T>::Bar);

而这个 A 定义:

template <class T>
struct A { typedef int Bar; };

这个电话:

int a;
foo(a);

T这个例子是什么?是int因为A<int>::Baris int,还是float因为 A<float>::Baris inttoo 或者任何你想要的......问题是你正在调用什么函数foo<int>(int)foo<float>(int)......

或者举个更接近问题的例子:

template <class T>
struct Foo {
   struct Bar {}; 
};

似乎编译器在解决这个问题时应该没有问题:

template <class T>
void resolve(typename Foo<T>::Bar*);

但是即使在这里编译器也有问题,因为不确定其他类的特化是否不会使用其他类的内部结构,如下所示:

template <class T>
struct Foo<T*> {
   typedef Foo<T>::Bar Bar; 
};

因此对于:

Foo<void>::Bar b;
resolve(&b);

编译器没有机会知道要调用哪个版本:

resolve<void>(Foo<void>::Bar*);
// or 
resolve<void*>(Foo<void>::Bar*);
//          ^   

我能给你什么建议 - 使用内联朋友 - 但用其他模板类来实现它。这行得通 - 但我确信这有点过度设计:

template <class S>
class ImplementSwap;

template <typename T>
class Foo {
    public:
    struct Bar {
        int a;
        Bar() {}
        ~Bar() {}
        friend class ImplementSwap<Foo<T>>;
        friend void swap(Foo<T>::Bar& b1, Foo<T>::Bar& b2)
        {  ImplementSwap<Foo<T>>::doSwap(b1, b2); }
        Bar(Bar&& b)  { swap(*this, b); }

    };
};

template <class T>
class ImplementSwap<Foo<T>> {
public:
   static void doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&);
};

template <class T>
void ImplementSwap<Foo<T>>::doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&) 
{
  // this one is not inline....
}

我公开了 Bar 来做这个测试:

Foo<int>::Bar a = Foo<int>::Bar(); // move constructor

int main() {
  swap(a,a); // explicit swap
}

[OLD] 我之前的回答是完全错误的,第一条评论指的是它。

friend void swap<>(typename Foo<T>::Bar&, typename Foo<T>::Bar&);
//              ^^

[/老的]

于 2012-10-13T18:54:51.483 回答
1

对我来说,这应该是这样的:

template <typename T>
class Foo
{
    struct Bar
    {
        template <typename V>
        friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
    };
};

template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) { }

template class Foo<int>;

int main()
{
   Foo<int>::Bar  bb1, bb2;
   swap<int>(bb1, bb2);
}  

这与 gcc 一起运行:http: //ideone.com/zZMYn

几点注意事项:

  1. 类内的友元声明创建在类外声明为友元的对象的前向声明。这适用于友元类、函数、模板。这意味着swap不需要您的转发声明。

  2. 语法some-name<..>用于模板特化和模板实例化。它不用于前向声明(包括友元声明)和定义。在您的示例中,您有一个前向声明、一个定义和一个调用(即函数模板的实例化)。

如果你调用函数 swap as swap(bb1, bb2);,编译器找不到它。对我来说,需要像上面的例子那样调用这个函数swap<int>更多的是编译器问题,而不是语言要求。编译器应该推导出模板函数调用的模板参数。

编辑

在上面的例子中 varsbb1bb2有 type Foo<int>::Bar。这意味着 的实例化swap应该是:

void swap(Foo<int>::Bar &b1, Foo<int>::Bar &b2) { }

没有任何其他实例可以在这里工作,因为 sayFoo<float>::BarFoo<int>::Bar. 没有办法转换Foo<int>::BarFoo<float>::Bar. 如果模板Foo<float>::Bar将被实例化,则无法使用该事件。参数的类型不同。

如果这个模板有几个专业化,情况可能会更复杂。但是在调用的时候,除了模板本身什么都没有。要考虑的是,专业化应该在调用点可见。

优秀的编译器可能能够处理这种情况。由于使用 explicitit 类型规范的工作是可用的并且似乎工作,我会说 gcc 的当前状态完全 Ok

于 2012-10-13T19:35:01.390 回答