Please, consider the following C++14 code:
#include <type_traits>
template<typename T>
class Bar {
static_assert(std::is_destructible<T>::value, "T must be destructible");
};
template<typename T>
void foo(Bar<T> const &) {}
template<typename T>
void foo(T const &) {}
class Product {
public:
static Product *createProduct() { return new Product{}; }
static void destroyProduct(Product *product) { delete product; }
private:
Product() = default;
~Product() = default;
};
int main()
{
Product* p = Product::createProduct();
foo(*p); // call 1: works fine
foo<Product>(*p); // call 2: fails to compile
Product::destroyProduct(p);
return 0;
}
And the error message from clang:
error: static_assert failed due to requirement 'std::is_destructible<Product>::value' "T must be destructible"
static_assert(std::is_destructible<T>::value, "T must be destructible");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in instantiation of template class 'Bar<Product>' requested here
foo<Product>(*p); // call 2: fails to compile
^
note: while substituting deduced template arguments into function template 'foo' [with T = Product]
foo<Product>(*p); // call 2: fails to compile
^
1 error generated.
My understanding is that both call 1
and call 2
should compile fine, but call 2
fails to compiler not only on clang but also on gcc and msvc.
From the standard perspective, is it correct that call 1
succeeds and call 2
fails to compile? Why?
Note: I know that I can work around the error by adding a std::enable_if
to the first overload of foo
, but I'd like to understand why call 1
is OK but call 2
is not.
Substitution failure is not an error is limited. If failure occurs beyond the "immediate context", the failure is hard and not prevented by SFINAE.
When you type foo<Product>(
?)
, it creates an overload set first. This overload set includes both overloads of foo
. But creating the first overload of foo leads to a static_assert
failure outside of the immediate context, so a hard error.
When you type foo(
?)
it also tries to create an overload set. Both of the templates are considered. It has no T
being passed in, so it attempts to deduce the T
from the arguments.
The argument is a Product&
. Deducing T const&
from a Product&
produces T=Product const
. Deducing Bar<T> const&
from a Product&
... fails to deduce a T
, as neither Product
nor any of its base classes are produced from a template of the form Bar<typename>
.
Without a T
deduced, the foo(Bar<T> const&)
overload is discarded. There is no way for T=Product
to be deduced, so no hard error from attempting to instantiate foo(Bar<Product> const&)
could occur.
In short, for call 2, you manually force a Bar<Product>
possibility into existence. For call 1, the compiler attempt to deduce the template arguments never gets there.
As a thought experiment, consider changing the declaration of Product
to:
class Product: public Bar<int> {
and leave everything else the same.
Now
foo(*p)
will generate 2 overload to consider - one
template<class T=Product const>
void foo(Product const&)
and one
template<class T=int>
void foo(Bar<int> const&)
with the second one being generated by looking at the base classes of Product
and finding a Bar<int>
. T
is deduced to Product
in one case, and int
in the other.
But when you do foo<Product>
you aren't relying on deduction to find T
- you are setting it manually. There isn't any deduction left to do.