Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to explicitly instantiate a template class that has a nested class with a friend function (C++)

Tags:

c++

templates

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.

like image 433
Zorawar Avatar asked Oct 13 '12 18:10

Zorawar


People also ask

How do you instantiate a template in a function?

To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.

Can we declare a template function as the friend of the class?

One-to-one: A template function instantiated with one set of template arguments may be a friend to one template class instantiated with the same set of template arguments. This is also the relationship between a regular non-template class and a regular non-template friend function.

What is the instantiation of the class template?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation.

Can a non-template class have a template function?

A non-template class can have template member functions, if required. Notice the syntax. Unlike a member function for a template class, a template member function is just like a free template function but scoped to its containing class.


2 Answers

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]

like image 176
PiotrNycz Avatar answered Oct 18 '22 12:10

PiotrNycz


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.

like image 32
Kirill Kobelev Avatar answered Oct 18 '22 13:10

Kirill Kobelev