10

Probably been asked before, but all this is approaching the limit of my comprehension and cognizance of C++, so I'm a little slow in understanding what's being talked about and exactly what's going on. Let me just jump straight to the code. This works:

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

But how do I move the definition of swap outside of the Bar struct body? If I do this:

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, flags: -Wall -std=c++11) reports:

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’

I guess the code for swap also needs to be created when explicitly instantiating Foo, which makes sense, but why can't the compiler figure out that swap(Foo<int>::Bar&...) needs to be created? Why does the template substitution fail? Or have I got everything wrong?

UPDATE 1

With:

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, flags: -Wall -std=c++11) reports:

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’

UPDATE 2

OK, so this can't be done. Piotr has linked to Output a nested class inside a template, but I don't understand the answer. Why can't swap be defined outside its declaration? As far as I (mis)understand things, why can't the compiler create code for swap(Foo<int>::Bar&...) and link to it in the code for the explicit instantiation of Foo<int>? Have I totally misunderstood what's going on? What's the problem?

UPDATE 3

OK, this can't be done because if there are template specializations the compiler can't guarantee calls to swap defined outside of Foo are unambiguous since Foo<some_class>::Bar might be something completely different in a particular specialization. I hope I've got that right. But, why doesn't g++ warn me about this before I create an explicit instantiation of 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.

This code compiles fine (g++ 4.7.1, flags: -Wall -std=c++11). But, shouldn't it warn me that this code could possibly cause problems? When I add the explicit instantiation of Foo, the problem isn't with that line itself, but with the swap code implemented outside of Foo.

Community
  • 1
  • 1
Zorawar
  • 6,505
  • 2
  • 23
  • 41
  • >> But, shouldn't it warn me that this code could possibly cause problems... I think it shouldn't. There is countless number of ways how you can change your code. Compiler cannot comment on all these possibilitities. This is more a subject of Source Code Analysis. Analysis can be of any type and take long. It can point at non elegant or difficult to understand constructs (that are fully legal), etc. – Kirill Kobelev Oct 14 '12 at 03:04
  • @Kirill: Yes, there are many ways I could change my code. Let us suppose I make such a change (at P). If that change causes an error to occur, fundamentally, at a different point in my code (at Q), then I would consider that code (at Q) to have been faulty regardless of the existence or nonexistence of the code at P. Especially if fixing the offending code (at Q) leads to no real change in the operation of my code (i.e. inlining `swap` makes no practical difference to defining it outside of the class body). At least gcc should provide a flag to just turn a warning on, surely? – Zorawar Oct 15 '12 at 03:30
  • I agree with you say for 30%. You are saying reasonable things. At the same time. Compiler vendors struggle without money. They have Everest of problems. And you want from them more. How much you payed for the copy of compiler that you are using? – Kirill Kobelev Oct 15 '12 at 03:35
  • Plus what you are asking is technically not easy. Can you give estimation how many men*years it will take to implement this and all other similar warnings? Plus compiler should work fast. Any additional checks should be either explicitly enabled or done with some other application (analysis tool). – Kirill Kobelev Oct 15 '12 at 03:38
  • @Kirill: I was going to suggest that this should be easy to spot and warn about, but I know even less about how gcc performs than I do about the intricacies of the c++ language, so I take your point. – Zorawar Oct 15 '12 at 23:22

2 Answers2

3

The problem is not with friend.

The problem is with this function itself:

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

Deducing template parameter T from nested class is not possible, see: Output a nested class inside a template or even better this answer: https://stackoverflow.com/a/4092248/1463922

To give example why it cannot be done, consider this function:

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

And this A definition:

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

And this call:

int a;
foo(a);

What is T in this example? Is it int because A<int>::Bar is int, OR float because A<float>::Bar is int too OR whatever you want.... The question is what function you are calling foo<int>(int) or foo<float>(int) or ...

Or to give example more closer to question:

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

It seems that compiler should have no problems with resolving this:

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

But compiler even here has problems because it is not sure if specialization of some other class will not use inner struct of other class, like this:

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

So for:

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

Compiler has no chance to know which version to call:

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

What can I advice you - use inline friend - but implement it with some other template class. This works - but I am sure this is a little over-engineered:

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....
}

I made Bar public to do this test:

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

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

[OLD] My previous answer was completely wrong, and first comments refer to it.

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

[/OLD]

Community
  • 1
  • 1
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • When I make that change g++ says: error: variable or field ‘swap’ declared void. Is there something else I need to do? I've looked at http://www.parashift.com/c%2B%2B-faq-lite/template-friends.html, but I can't figure out what I need to do for a nested class. – Zorawar Oct 13 '12 at 19:00
  • Hmm. I copied and pasted your code just to make sure, but g++ reports: "error: template-id ‘swap<>’ for ‘void swap(Foo::Bar&, Foo::Bar&)’ does not match any template declaration" at the point in the code where `swap` is declared in the `Bar` struct body. I'll update the question... – Zorawar Oct 13 '12 at 19:13
  • You are right and I was wrong. I found the answer answering why it is not possible. And I give you workaround. Maybe better would be to stay with inline friend? – PiotrNycz Oct 13 '12 at 19:58
  • Yeah, I guess I'll just stay with the initial code and inline it. For purely stylistic reasons I wanted the method to be implemented outside of its declaration, and your suggested code still has inline code, so for me I don't gain anything. Thanks for the answer, though. What I don't understand, though, is _why_ I can't do it. I don't understand the answer in the question you linked to. – Zorawar Oct 13 '12 at 20:32
  • Last function is not inline: `SwapImplement<...>::doSwap`. For the linked answer - this is most important part: "If there are some specializations of the foo template, there might be several foos with the same inner bar class. The compiler can then not figure out what foo::bar is, unless it instantiated foo for all possible Ts. The standard says that it doesn't have to do that." – PiotrNycz Oct 13 '12 at 20:44
  • OK, I (think) I've got it now. Thanks a lot. Last question, though: why didn't gcc warn me about this before I added the explicit instantiation of `Foo`? I'll update my question... – Zorawar Oct 13 '12 at 21:08
  • I do not know why gcc did not warn you before. Error diagnostic is very complicated task for compiler creators - it is sometimes more complicated that the actual compiler work - so C++ std does not require to provide diagnostic for every possible error. It is just matter of effort compiler vendor is going to put in diagnostic versus effort on let say implementation of new C++11 features... – PiotrNycz Oct 13 '12 at 21:18
  • Yeah, C++11 looks like a completely new language to me! I was just about getting by with c++98 and a little Boost, but now...! – Zorawar Oct 13 '12 at 21:23
  • Your trick with that wrapper class is correct. Its core most important part is `>::doSwap`. The whole idea is to move the spec of the type from the user code into the wrapper. Your solution can be simplified from outer calss to just an outer template function and an inline call of this outer template function with passing type in the same way as you did. – Kirill Kobelev Oct 14 '12 at 01:31
1

For me this should look like this:

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

This runs with gcc: http://ideone.com/zZMYn

Few notes:

  1. Friend declaration inside the class creates forward declaration of the object that is declared as friend outside of the class. This applies to friend classes, functions, templates. This means that forwards decl of your swap is not needed.

  2. Syntax some-name<..> is used in template specializations and template instantiations. It is not used in forward declarations (including friend declarations) and definitions. In your example you have one forward declaration, one definition and one call (that is an instantiation for templates of the functions).

If you call the function swap as swap(bb1, bb2);, compiler cannot find it. For me the need to call this function as in the example above swap<int> is more a compiler problem rather than a language requirement. Compiler should deduce template params for template function calls.

EDIT

In the example above the vars bb1 and bb2 have type Foo<int>::Bar. This means that the instantiation of swap should be:

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

No any other instantiation can work here because say Foo<float>::Bar is a different type from Foo<int>::Bar. There is no way to convert Foo<int>::Bar into Foo<float>::Bar. Event if template for Foo<float>::Bar would be instantitated, it cannot be used. The types of params are different.

If there would be several specializations for this template around, the situation might be more complicated. But at the point of the call there is nothing than the template itself. To be considered, specialization should be visible at the point of the call.

Excellent compiler might be able to handle this case. Since the work around with explictit type specification is available and seems to work, I would say that the current state of gcc fully Ok.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
  • Yeah, that compiles on mine too. But what happens when you add a method that actually calls `swap` (the copy constructor, for example, in my code above)? On my system the code then doesn't compile. – Zorawar Oct 13 '12 at 19:41
  • It cannot be done, see: http://stackoverflow.com/questions/4351567/output-a-nested-class-inside-a-template. I proposed a trick to achieve this with friend class for parent class - but maybe better is to use just inline friend. – PiotrNycz Oct 13 '12 at 20:02
  • These post speak about the same solution as I proposed. There is nothing undeduceable in my code. At the point of template instantiation all types are known. Most likely gcc implemented this properly. This is why it runs on ideone. Maybe it was not working in earlier versions of the compiler. – Kirill Kobelev Oct 13 '12 at 20:13
  • The problem is that in the code that I'm actually dealing with I declare the inner struct `Bar` private because only `Foo` uses it. I never call `swap` outside of `Foo`. Then, it appears I can't define `swap` outside of its declaration. – Zorawar Oct 13 '12 at 20:19
  • It is true that when compiler processes inline definition it knows that this is primary `Foo` and not one its specializations. In the outside case the task is more complicated. Nevertheless this looks like a compiler problem. Proper compiler should handle this. Even for a complicated case with outside defn. – Kirill Kobelev Oct 13 '12 at 20:27
  • @KirillKobelev see update to my answer. THIS IS NOT A COMPILER BUG, this is just C++, and there are reasons for this. – PiotrNycz Oct 13 '12 at 21:02
  • @Piotr, I looked at your post. In fact, your example with `int a; foo(a);` is valid and there is no way to deduce the template param. But this case is different from the OP and the answer that I am giving. This means that it proves nothing. – Kirill Kobelev Oct 14 '12 at 01:37
  • @Piotr, one more aspect. I never stated that this is compiler bug. Bug is when something is wrong and it is clear that it **should work** and how. Problem is a more broad term. It also includes situation when something works more or less fine but might work better. Support wider set of types, have better scalability, etc. p.s. Thanks for your participation. – Kirill Kobelev Oct 14 '12 at 01:44
  • @KirillKobelev I added the second example strictly related to question. See my answer. My first example was the easiest case to understand. The entire problem is that compiler to be sure how to resolve the call of inner class - the compiler should know about every possible template specialization. It does not matter that in question there is no specialization - compiler cannot guess that there will be no specialization later in the code; we have two phases compilation of templates - during first phase it is not possible to know about specialization. For more ask question on comp.lang.c++.std. – PiotrNycz Oct 14 '12 at 09:54
  • @Poitr, your example with template specialization is creating ambiguity. This is true. But in the question and in my answer there are no specializations. There is only the main template. Compiler should use this template because there is no ambiguity. Adding specialization will create ambiguity, but this will be different code. I do not see logic errors in what I am writing. – Kirill Kobelev Oct 15 '12 at 03:48