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:
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:
However, the question remains: Why does a defaulted template parameter of a function that is never used lead to compilation failure?
When instantiating the class template B<A>
, the compiler instantiates the declaration, but not the definition of B<A>::f1
:
The implicit instantiation of a class template specialization causes
- the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
- [...]
The implicit instantiation of a class template specialization does not cause the implicit instantiation of default arguments or noexcept-specifiers of the class member functions.
However, that just means that the function body isn't instantiated; the rest is.
If f1
had default arguments, those wouldn't be instantiated, but the std::enable_if
is a default template argument so this exception doesn't apply.
The following gets instantiated:
template<typename = std::enable_if<std::is_copy_constructible<A>::value>> void f1() {}
The default template argument is ill-formed because std::is_copy_constructible
requires:
T
shall be a complete type, cvvoid
, or an array of unknown bound.
In C++20, using a trailing requires-clause and the std::copy_constructible
concept:
void f1() requires std::copy_constructible<T> {}
In C++17:
template<typename U = T>
std::enable_if_t<std::is_copy_constructible_v<U>> f1() {}
This works because std::enable_if_t
depends on a template parameter to the current function template, so SFINAE can take place as intended.
See std::enable_if to conditionally compile a member function for more strategies.