[clang] Potential SFINAE regression with Clang-16
Recently while upgrading from Clang 15, to Clang 16 I've come across of a piece of SFINAE that no longer works. I'm not sure if this is a bug, or should never have worked in the first place. Here is a Godbolt demonstrating the issue: https://godbolt.org/z/GbE53Wd3n. Apologies that the repro is somewhat complex, but I couldn't seem to reduce it further.
The crux of the issue is that std::enable_if_t<!is_std_vector<typename LastArg<Args...>::type>, bool> = true> where the last variadic type is some std::vector<T> used to trigger substitution failure, but no longer does. If we write the SFINAE guard std::enable_if_t<is_std_vector<typename LastArg<Args...>::type>, bool> = false> it works. Thanks!
Here is the same sample included in the Godbolt link:
#include <type_traits>
#include <vector>
#include <iostream>
#include <tuple>
#include <map>
// template to get last argument of variadic template
template<typename... Args>
struct LastArg {
using type =
typename std::tuple_element<sizeof...(Args) - 1, std::tuple<Args...>>::type;
};
template<>
struct LastArg<> {
using type = void;
};
// template to check if a type is any specialization of a templated class
template<template<class, class...> class, class>
struct is_instantiation_of : public std::false_type {};
template<template<class, class...> class T, class TInterface>
struct is_instantiation_of<T, T<TInterface>> : public std::true_type {};
// template to check if a type is any specialization of std::vector
template<typename T>
constexpr bool is_std_vector =
is_instantiation_of<std::vector, T>::value;
// two SFINAE guarded methods, one to be used if the last argument supplied is not a std::vector<T>
// the negation in the SFINAE doesn't work and both functions are considered for a call with std::vector<int>
template<typename... Args,
std::enable_if_t<!is_std_vector<typename LastArg<Args...>::type>, bool> =
true>
// comparing to false instead of negating with ! seems to work
// std::enable_if_t<is_std_vector<typename LastArg<Args...>::type>, bool> =
// false>
void Foo(Args&&... args) {
std::cout << "Foo";
}
template<typename... Args, typename U>
void Foo(Args&&... args, const std::vector<U>& allocator)
{
std::cout << "Bar";
}
int main() {
std::vector<int> int_vec;
Foo(const_cast<const std::vector<int>&>(int_vec));
}
@llvm/issue-subscribers-clang-frontend
I am not sure how this ever worked.
const vector