1

This C++ code builds successfully:

void h(int *)
{
}

template <typename T>
class A
{
public:
    void f()
    {
        T *val;
        
        h(val);
    }
};

int main()
{
    A<const int> a;
}

Why?

It is undeniable that A<const int>::f() cannot work.

If you call a.f(), it fails to build as expected.

Why is an instance of A<const int> even allowed to exist then?

I'm not sure how SFINAE applies here.

I'd appreciate a C++ standard quote or a relevant link.

eepp
  • 7,255
  • 1
  • 38
  • 56

2 Answers2

6

It's allowed to exist because it can still be useful for it to exist.

Consider std::vector, which has a one-argument overload of resize, which default-constructs new elements. Definitely useful! But it's also useful to have a std::vector of some type which isn't default-constructible, when you don't intend to resize it in that way. Forcing all member functions to be available, even those not needed by the user, would artificially restrict the applicability of a class.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • I understand that it's a nice language feature, and in fact I need it, so I'm glad it exists. I'm just looking for an official statement which confirms that this is expected behaviour. – eepp Dec 08 '20 at 16:07
  • 3
    @eepp - In that case, your question is answered by the suggested duplicate https://stackoverflow.com/questions/23679525/why-are-some-functions-within-my-template-class-not-getting-compiled – StoryTeller - Unslander Monica Dec 08 '20 at 16:13
1

There's no sfinae here. If we look closely at the declaration of the function, and not it's implementation:

template <typename T>
class A
{
public:
    void f();
};

You can see that there's nothing to tell the compiler that this function should not be part of overload resolution.

If we add a return type containing an expression, this is different:

template <typename T>
class A
{
public:
    auto f() -> decltype(void(h(std::declval<T*>())));
};

That's different. Now The compiler need to resolve h(T*) to perform overload resolution. If the substitution fails (the instantiation of the function declaration) then the function is not part of the set.


As for why the code still compiles, take a look at this:

void h(int *)
{
}

template <typename T>
class A
{};

template<typename T>
void f()
{
    T *val;
        
    h(val);
}


int main()
{
    A<const int> a;
}
  1. It's undeniable that f(A<const int>) cannot work
  2. If you call f(a), it fails to build as expected.
  3. Why is an instance of A<const int> even allowed to exist then?

The answer should become obvious now: the function is never instantiated!

The very same applies for member functions. There is no use to instantiate all member function if you don't use them.


If you look for an official statement that comfirm this behavior, then look no further than the standard itself.

From [temp.inst]/4 (emphasis mine):

Unless a member of a class template or a member template is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist or if the existence of the definition of the member affects the semantics of the program; in particular, the initialization (and any associated side effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Ok thanks a lot Guillaume. I'm going to accept this one as it's very complete and answers my question. – eepp Dec 08 '20 at 16:25