Why class template instantiation fails for unused member template function

Viewed 271

Whats wrong with this:

#include <type_traits>

struct A;

template<typename T>
struct B
{
    template<typename=std::enable_if<std::is_copy_constructible<T>::value>>
    void f1() {}
};

template<typename T>
struct C {};


// Type your code here, or load an example.
int main() {
    // Following fails
    B<A> b;
    // Could use this:
    // b.f1<C>();

    // This complies
    C<A> c;

    return 0;
}

/* This to be in or not doesn't make a difference
struct A
{};
*/

I tried this here: https://godbolt.org/z/NkL44s with different compilers:

  • x86-64 gcc 9.2: compiles
  • x86-64 gcc (trunk): fails
  • x86-64 clang 6.0.0: compiles
  • x86-64 clang 7.0.0 and later: fails
  • x64 msvc v19.22: compiles
  • x64 msvc v19.23 (tested internally): fails

So why do more recent compilers reject this? When instantiating B<A> it is not clear in which form f1 will be used or if it will be used at all. So why the compiler complains about it? Shouldn't the f1 member template function be checked only if it is really used?


Edit:
As mentioned in comments, I made an unintented mistake in above code: std::enable_if should have been std::enable_if_t, as in this corrected playground: https://godbolt.org/z/cyuB3d

This changes the picture of compilers passing this code without error:

  • gcc: fails
  • clang: fails
  • x64 msvc v19.22: compiles
  • x64 msvc v19.23 (tested internally): fails

However, the question remains: Why does a defaulted template parameter of a function that is never used lead to compilation failure?

2 Answers

The reason is that std::is_constructible requires a complete type: (Table 42)

Template

template <class T>
struct is_­copy_­constructible;

Preconditions

T shall be a complete type, cv void, or an array of unknown bound.

Failing to meet a library "shall" requirement results in undefined behavior.

From cppreference on is_copy_constructible<T>:

T shall be a complete type, (possibly cv-qualified) void, or an array of unknown bound. Otherwise, the behavior is undefined.

So it seems like you have just plain UB in older compiler versions while the newer ones are nice enough to tell you that A has to be a complete type.

Note that in the presence of UB, compliers are not reuqired to issue an error, but they may do it, which is a nice thing.

Related