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 offoo. But creating the first overload of foo leads to astatic_assertfailure 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 noTbeing passed in, so it attempts to deduce theTfrom the arguments.The argument is a
Product&. DeducingT const&from aProduct&producesT=Product const. DeducingBar<T> const&from aProduct&... fails to deduce aT, as neitherProductnor any of its base classes are produced from a template of the formBar<typename>.Without a
Tdeduced, thefoo(Bar<T> const&)overload is discarded. There is no way forT=Productto be deduced, so no hard error from attempting to instantiatefoo(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
Productto:and leave everything else the same.
Now
will generate 2 overload to consider - one
and one
with the second one being generated by looking at the base classes of
Productand finding aBar<int>.Tis deduced toProductin one case, andintin the other.But when you do
foo<Product>you aren't relying on deduction to findT- you are setting it manually. There isn't any deduction left to do.