10

In the code below function f() can call the operator bool() and operator *() member functions of unique_ptr<C> for the incomplete class C. However when function g() tries to call those same member functions for unique_ptr<X<C>>, the compiler suddenly wants a complete type and tries to instantiate X<C>, which then fails. For some reason unique_ptr<X<C>>::get() does not cause template instantiation and compiles correctly as can be seen in function h(). Why is that? What makes get() different from operator bool() and operator *()?

#include <memory>

class C;
std::unique_ptr<C> pC;

C& f() {
    if ( !pC ) throw 0; // OK, even though C is incomplete
    return *pC;         // OK, even though C is incomplete
}

template <class T>
class X
{
    T t;
};

std::unique_ptr<X<C>> pX;

X<C>& g() {
    if ( !pX ) throw 0; // Error: 'X<C>::t' uses undefined class 'C'
    return *pX;         // Error: 'X<C>::t' uses undefined class 'C'
}

X<C>& h() {
    if ( !pX.get() ) throw 0; // OK
    return *pX.get();         // OK
}

class C {};
Barnett
  • 1,491
  • 12
  • 18
  • I have no problem compiling your code in Visual Studio 2013. What compiler do you use? – Ionel POP Sep 15 '16 at 09:40
  • I tested with Visual Studio 2015, GNU and Clang. – Barnett Sep 15 '16 at 09:41
  • Does each of the lines of code with "error" give an error if used alone? Or is there an error only if there are two of them in the same function? – anatolyg Sep 15 '16 at 09:51
  • Yea, each line gives an error. – Barnett Sep 15 '16 at 09:53
  • @Barry The answer provided in the question you linked as duplicate unfortunately is over my head. I am just going to use the workaround (as in `h()`) and hope I don't get burnt somehow. – Barnett Sep 15 '16 at 16:54
  • @Barry reopened as the explanation in the duplicate does not explain it. In the isocpp mail thread T.C. thinks it's ADL lookup, others dispute that ADL should take place. I think an explanation is needed. – Richard Hodges Sep 15 '16 at 17:22
  • @RichardHodges Yet it's the same question which will have the same answer. If there's a different answer, it should get posted on that question. Moreover, neither answer here even attempts to answer the question. – Barry Sep 15 '16 at 17:40
  • @Barry we're waiting for an expert to help us. I have followed the reasoning in the other answer but it loses me. It seems to pull together unrelated material. If unique_ptr can be deduced, then why not unique_ptr>, since there is no competing specialisation of unique_ptr<>. All names are known at this point. Under what reasonable circumstances would ADL be invoked? – Richard Hodges Sep 15 '16 at 17:44
  • @Barry furthermore, since it compiles on 1 compiler and fails on 3, between 1 and 3 of them are wrong. Either that, or it's "implementation defined" or UB. – Richard Hodges Sep 15 '16 at 17:46
  • @Barry and finally (sorry to go on) this question is different. There is the implicit conversion rule that says that since bool(p) is well defined, so should !p: http://en.cppreference.com/w/cpp/language/implicit_conversion – Richard Hodges Sep 15 '16 at 17:51
  • @RichardHodges We have to look up `!pX`. That's ADL. That's why that one fails but all the calls to member functions succeed. – Barry Sep 15 '16 at 18:08
  • @Barry surely `!pX` is implicit since `unique_ptr>::operator bool` is available (and recognised without ADL), in exactly the same way that `unique_ptr::operator bool` is available (seemingly also without ADL). I don't think I am alone in not seeing a difference. Note that `!pC` works as expected. What is special about the second level of template indirection, specifically and only in relation to the implicit conversion rule when applying `operator!`? – Richard Hodges Sep 15 '16 at 18:17
  • @RichardHodges See my answer, it doesn't matter that `operator bool` is available, we still have to do overload resolution which still requires doing name lookup on all the viable candidates first. – Barry Sep 15 '16 at 18:19

2 Answers2

7

Here's a contrived and simplified example, using only our own types:

class Incomplete;

template <class T>
struct Wrap {
    T t;
};

template <class T>
struct Ptr {
    T* p;

    void foo() { }
};

template <class T>
void foo(Ptr<T> ) { }

int main() {
    Ptr<Incomplete>{}.foo();         // OK
    foo(Ptr<Incomplete>{});          // OK

    Ptr<Wrap<Incomplete>>{}.foo();   // OK
    ::foo(Ptr<Wrap<Incomplete>>{});  // OK!
    foo(Ptr<Wrap<Incomplete>>{});    // error
}

The problem is, when we make an unqualified call to foo, as opposed to a qualified call to ::foo or calling the member function Ptr<T>::foo(), we're triggering argument-dependent lookup.

ADL will look in the associated namespaces of template types in class template specializations, which will trigger implicit template instantiation. Template instantiation needs to be triggered in order to perform ADL lookup because, for instance, Wrap<Incomplete> could declare a friend void foo(Ptr<Wrap<Incomplete >> ) which would need to be invoked. Or Wrap<Incomplete> might have dependent bases whose namespaces need to also be considered. Instantiation at this point makes the code ill-formed because Incomplete is an incomplete type and you can't have a member of an incomplete type.

Getting back to the original question, the calls to !pX and *pX invoke ADL which leads to the instantation of X<C> which is ill-formed. The call to pX.get() does not invoke ADL, which is why that one works fine.


See this answer for more details, also CWG 557.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    Then why don't we also have to do overload resolution at !pC? At this point, C is not complete either. – Richard Hodges Sep 15 '16 at 18:20
  • 2
    @RichardHodges We DO do overload resolution of `!pC`. Nothing in there requires `C` to be complete - it's not a template (won't be instantiated). `!pX` required instantiation of `X` which is ill-formed. – Barry Sep 15 '16 at 18:21
  • OK, light is dawning... why does X need to be instantiated? Nothing depends on it. What part of the standard prioritises ADL over implicit conversion to bool when applying `operator!` - given that operator! is a special case in an implicit bool context? – Richard Hodges Sep 15 '16 at 18:23
  • 2
    @RichardHodges Step 1 in overload resolution is finding all the names. Finding the names invokes ADL. ADL triggers instantiation. – Barry Sep 15 '16 at 18:25
  • Got it. Many thanks. ^^ this should be the answer. Particularly mentioning that in the light of the special rule around some of the operators. The cppreference page is otherwise misleading. – Richard Hodges Sep 15 '16 at 18:26
  • @RichardHodges The "special" operators are all those that are allowed to be non-members, so all except `=` `()` `[]` `->`. That's because ADL only applies to non-member functions. So e.g. `swap(pX,pY)` would also trigger instantiation. – Oktalist Sep 15 '16 at 22:13
  • @Barry I don't understand ADL very well so I am going to have to go read up on that before coming back here, but for the sake of completeness could you perhaps add to your example another `foo()` that would end up being called after template instantiation takes place? – Barnett Sep 16 '16 at 05:59
5

It's not the unique_ptr that requires the complete type, it's your class X that does.

std::unique_ptr<C> pC;

You don't actually do any allocation yet for C so the compiler doesn't need to know the specifics of C here.

std::unique_ptr<X<C>> pX;

Here, you use C as a template type for X. Because X contains an object of type T which is C here the compiler needs to know what to allocate when X is instantiated. (t is an object and thus instantiated on construction). Change T t; to T* t; and the compiler wouldn't complain.

Edit:

This does not explain why h() compiles, yet g() does not.

This example compiles fine:

#include <memory>

class C;
std::unique_ptr<C> pC;

C& f() {
    if (!pC) throw 0; // OK, even though C is incomplete
    return *pC;         // OK, even though C is incomplete
}

template <class T>
class X
{
    T t;
};

std::unique_ptr<X<C>> pX;

typename std::add_lvalue_reference<X<C>>::type DoSomeStuff() // exact copy of operator*
{
    return (*pX.get());
}

void g() {
    if ((bool)pX) return;
}

class C {};

int main()
{
    auto z = DoSomeStuff();
}

Which makes it even more interesting as this mimics the operator* but does compile. Removing the ! from the expression also works. This seems to be a bug in multiple implementations (MSVC, GCC, Clang).

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • This explanation doesn't make much sense, if the compiler "needs to know what to allocate" then `C` is just as much a problem as `X` – M.M Sep 15 '16 at 09:42
  • 1
    @M.M As Ivan said, as long as the class that has a template instantiation does _not_ use a direct object of type `T` (unique_ptr uses `T*`, X does use one) then it's fine and dandy. – Hatted Rooster Sep 15 '16 at 09:45
  • @IvanRubinson how is that relevant? – M.M Sep 15 '16 at 09:45
  • 7
    This does not explain why `h()` compiles, yet `g()` does not. – Barnett Sep 15 '16 at 09:45
  • 1
    @M.M `unique_ptr` can take incomplete types because it only has pointers to those types. – Ivan Rubinson Sep 15 '16 at 09:46
  • @Barnett try using `h()` with `g()` commented out, `h()` will give a compilation error as well. Interesting behaviour. – Hatted Rooster Sep 15 '16 at 10:29
  • @Gill Bates That is not what I am getting. `h()` compiles just fine without `g()`. What compiler are you using? – Barnett Sep 15 '16 at 10:33
  • @barnett Did you try calling it? That's what I meant with "using" it. – Hatted Rooster Sep 15 '16 at 10:35
  • @Gill Bates Adding `int main() { h(); }` to the bottom and it still compiles (with `g()` removed). See: https://godbolt.org/g/Xgyrnd – Barnett Sep 15 '16 at 10:40
  • @Gill Bates I think there is an issue with using template&unique_ptr. This compiles http://ideone.com/vgI3UZ , but not if you put the definition of the class U before the pX – Ionel POP Sep 15 '16 at 12:18
  • @ionelPOP mhm, it does compile because at the time of defining the function `U` is still not known, once the compiler knows `U` contains some other forward declared class it goes ballistic. – Hatted Rooster Sep 15 '16 at 12:22
  • Like you, I cannot let this go. I have simplified the problem and asked on the isocpp forum under the subject "Is this a compiler bug, QoI problem or standard behaviour?" – Richard Hodges Sep 15 '16 at 13:30
  • @RichardHodges Great, let me know if anything comes up. – Hatted Rooster Sep 15 '16 at 13:36
  • This doesn't address the main issue in the question. Your "edit" just copies the same code from OP that already worked. See the linked question for the real answer. – Barry Sep 15 '16 at 15:52
  • @Barry Actually I didn't, I was legitimately surprised by the behaviour so I thought it'd have something to do with the operator call, and it did, glad you found a duplicate though as this was killing me. – Hatted Rooster Sep 15 '16 at 16:01
  • ADL is required to search for any viable non-member `operator!` in associated namespaces, including namespaces of base classes of `X`, if any; that requires instantiating `X` to find its base classes. – Oktalist Sep 15 '16 at 16:34
  • @Oktalist Do you know of some way to prevent this instantiation that takes place in `g()`? – Barnett Sep 15 '16 at 17:05
  • Probably by explicitly calling one of the operators. – Hatted Rooster Sep 15 '16 at 17:07
  • @Gill, that is not very pretty. Plus, will that always work, ie guarantee that instantiation will not take place? – Barnett Sep 15 '16 at 17:10
  • Yes I believe it's guaranteed by §14.7.1¶1. As shown in the question you can also do e.g. `!pX.get()`. – Oktalist Sep 15 '16 at 17:35