6

How to compile the following code?

#include <type_traits>
#include <utility>

struct A;

template<typename T>
struct B{
    T* p;

    B& operator=(B&&);
    B& operator=(T&&);
};

int main(){
    //typedef B<A> type;// fine
    typedef B<std::pair<A, A>> type;// error

    noexcept(std::declval<type&>() = std::declval<type>());

    return 0;
}

PS: Type B simulates the boost::recursive_wrapper which fails to compile for the same reason.

cqdjyy01234
  • 1,180
  • 10
  • 20

2 Answers2

8

The typedef itself isn't the problem. It's perfectly legal to write struct foo; typedef std::pair<foo, foo> bar;. The problem is with

noexcept(std::declval<type&>() = std::declval<type>());

This requires the compiler to perform overload resolution for operator=. As part of overload resolution it must look for possible conversions from B&& to std::pair<A, A>&&, and that requires instantiating std::pair<A,A> (§14.7.1 [temp.inst]/p6):

A class template specialization is implicitly instantiated if the class type is used in a context that requires a completely-defined object type or if the completeness of the class type might affect the semantics of the program. [ Note: In particular, if the semantics of an expression depend on the member or base class lists of a class template specialization, the class template specialization is implicitly generated. For instance, deleting a pointer to class type depends on whether or not the class declares a destructor, and conversion between pointer to class types depends on the inheritance relationship between the two classes involved. —end note ]

...and, by §17.6.4.8 [res.on.functions]/p2, this instantiation causes undefined behavior.

Although the compiler is not required to instantiate std::pair<A, A> in this context since the move assignment operator is an exact match (§14.7.1 [temp.inst]/p7):

If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • I think you catch the point. So how to bypass the problem? You may study boost::recursive_wrapper directly. – cqdjyy01234 Sep 19 '14 at 02:13
  • @user1535111 I can't make any sense out of some of the compilers' behavior here. So [I've asked a question](http://stackoverflow.com/questions/25925551/gcc-and-clang-implicitly-instantiate-template-arguments-during-operator-overload). – T.C. Sep 19 '14 at 02:47
2

You have to actually put in the entire declaration of A before your use it in another declaration. A forward reference won't be enough.

Logicrat
  • 4,438
  • 16
  • 22