5
votes

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
It's typename = std::enable_if_t<...>, not typename = std::enable_if<...>. - L. F.
can you include an error message? Are all compilers complaining in the same way? - 463035818_is_not_a_number
A simplified case: template<bool = std::is_copy_constructible_v<T>> void f1() {}. The problem seems to be related to the time of resolution of default template arguments. Note that T being incomplete type results in undefined behaior. The differences I can observe are caused by compiler checks for type completeness. - Daniel Langr
@L.F. I don't think it matters here. SFINAE is not applied and the anonymous template parameter may as well refer to the std::enable_if type itself. - Daniel Langr
I added a paragraph to the post responding to the correctly spotted misuse of std::enable_if versus std::enable_if_t. - Jojakim Stahl

2 Answers

3
votes

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.

2
votes

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.